diff --git a/.claude/skills/b24phpsdk-maintainer/SKILL.md b/.claude/skills/b24phpsdk-maintainer/SKILL.md index fa03d555..f5661247 100644 --- a/.claude/skills/b24phpsdk-maintainer/SKILL.md +++ b/.claude/skills/b24phpsdk-maintainer/SKILL.md @@ -642,6 +642,22 @@ the Bitrix24 REST payload format at the service boundary, following existing ser patterns. Do not expose raw date/time strings in service method arguments when the SDK can accept a typed immutable date value instead. +### Field metadata methods + +When a REST API entity exposes `*.field.get` and `*.field.list`, implement those methods in +a dedicated field metadata service instead of adding `fieldGet()` or `fieldList()` methods to +the primary entity service. + +Use this shape: + +- service class: `Services\\Field\Service\Field` +- builder accessor: `Field()` +- public methods: `get(string $name, array $select = [])` and `list(array $select = [])` +- result wrappers: `FieldResult`, `FieldsResult`, and `FieldItemResult` + +If one issue covers several entities with field metadata endpoints, create one field service +per entity unless the scope already has an established shared field-service convention. + ### ApiEndpointMetadata documentation links When adding or changing `ApiEndpointMetadata` attributes, documentation links must point to diff --git a/.tasks/517/plan.md b/.tasks/517/plan.md new file mode 100644 index 00000000..81fe4238 --- /dev/null +++ b/.tasks/517/plan.md @@ -0,0 +1,280 @@ +# Plan: Add humanresources.* v3 service - org structure (issue #517) + +## Context + +Issue #517 adds typed SDK support for the Bitrix24 REST API v3 `humanresources.*` scope. +The worktree is `/private/tmp/b24phpsdk-517-add-humanresources-scope` on branch +`feature/517-add-humanresources-scope`, based on `origin/v3-dev`. + +The local OpenAPI snapshot was refreshed with: + +```bash +make -s oa-schema-build BITRIX24_WEBHOOK="" +``` + +Baseline unit tests passed before implementation: + +```bash +make test-unit +# OK (969 tests, 2723 assertions) +``` + +Official Bitrix24 documentation was checked for all 24 methods. The OpenAPI snapshot is not +complete enough to drive implementation blindly: + +- several write/count methods have empty parameter schemas in `docs/open-api/openapi.json` +- `humanresources.employee.search`, `humanresources.node.list`, `humanresources.node.search`, + `humanresources.node.children`, and all field-list methods are documented as `result.items` + even when the snapshot exposes a flat array +- `humanresources.node.list` and `humanresources.node.search` require `type` +- field `get` methods return `result.item` +- count/multidepartment/subordinates/communication/member mutation methods return named payload + objects directly under `result` + +Use official docs as the source of truth for method parameters and result envelopes. + +Generator rule: + +- Run `php bin/console b24-dev:result-item-generator --stage=all` before manual + edits for generated `*ItemResult.php` classes. +- Use the generated result-item files as starting points, then correct names, namespaces, + nullability, and response-envelope wrappers against the official docs. + +## API Surface + +### Employee service + +Methods: + +- `count(): EmployeeCountResult` for `humanresources.employee.count` +- `multidepartment(): EmployeeMultidepartmentResult` +- `search(string $name, ?int $nodeId = null, array $select = []): EmployeesResult` +- `subordinates(int $id): EmployeeSubordinatesResult` + +### Node service + +Methods: + +- `add(string $type, string $name, int $parentId, array $optional = []): NodeResult` +- `children(int $id, array $select = []): NodesResult` +- `count(): NodeCountResult` +- `edit(int $id, array $fields): NodeResult` +- `get(int $id, array $select = []): NodeResult` +- `list(string $type, array $select = [], array $pagination = []): NodesResult` +- `move(int $id, int $parentId): NodeResult` +- `search(string $type, string $name, ?int $parentId = null, array $pagination = []): NodesResult` + +### Field metadata services + +`*.field.get` and `*.field.list` methods live in dedicated field services, not in the +primary entity services: + +- `EmployeeField::get(string $name, array $select = []): EmployeeFieldResult` +- `EmployeeField::list(array $select = []): EmployeeFieldsResult` +- `NodeField::get(string $name, array $select = []): NodeFieldResult` +- `NodeField::list(array $select = []): NodeFieldsResult` +- `NodeMemberField::get(string $name, array $select = []): NodeMemberFieldResult` +- `NodeMemberField::list(array $select = []): NodeMemberFieldsResult` + +### NodeCommunication service + +Methods: + +- `edit(int $nodeId, string $communicationType, array $options = []): NodeCommunicationEditResult` +- `list(int $id): NodeCommunicationResult` + +### NodeMember service + +Methods: + +- `add(int $nodeId, array $userIds, string $role): NodeMemberOperationResult` +- `move(int $nodeId, array $userIds, ?string $role = null): NodeMemberOperationResult` +- `remove(int $nodeId, array $userIds): NodeMemberRemoveResult` +- `set(int $nodeId, array $userIds): NodeMemberOperationResult` + +Batch support: + +- Add `Service/Batch.php` for list/add methods that are safe for batch in this issue: + `employee.search`, `node.children`, `node.list`, `node.search`, and `node.member.add`. +- Do not batch methods whose docs explicitly return `ERROR_BATCH_METHOD_NOT_ALLOWED` during + live verification; document any exclusions in this plan before implementation changes. + +## Files to Create + +### 1. `src/Services/HumanResources/HumanResourcesServiceBuilder.php` + +Creates and caches: + +- `employee(): Service\Employee` +- `employeeField(): EmployeeField\Service\EmployeeField` +- `node(): Service\Node` +- `nodeField(): NodeField\Service\NodeField` +- `nodeCommunication(): Service\NodeCommunication` +- `nodeMember(): Service\NodeMember` +- `nodeMemberField(): NodeMemberField\Service\NodeMemberField` + +Add `#[ApiServiceBuilderMetadata(new Scope(['humanresources']))]`. + +### 2. `src/Services/HumanResources/Service/*.php` + +Create: + +- `Service/Employee.php` +- `Service/Node.php` +- `Service/NodeCommunication.php` +- `Service/NodeMember.php` +- `Service/Batch.php` +- `EmployeeField/Service/EmployeeField.php` +- `NodeField/Service/NodeField.php` +- `NodeMemberField/Service/NodeMemberField.php` + +Each service must use `ApiVersion::v3` in `core->call(...)`. +Every method must have `#[ApiEndpointMetadata(...)]` with the English docs URL. + +### 3. `src/Services/HumanResources/Result/*.php` + +Create result wrappers and item classes: + +- `EmployeeItemResult.php`, `EmployeesResult.php` +- `EmployeeCountResult.php` +- `EmployeeMultidepartmentResult.php` +- `EmployeeSubordinatesResult.php` +- `NodeItemResult.php`, `NodeResult.php`, `NodesResult.php`, `NodeCountResult.php` +- `NodeMemberItemResult.php` +- `NodeCommunicationResult.php`, `NodeCommunicationEditResult.php` +- `NodeMemberOperationResult.php`, `NodeMemberRemoveResult.php` +- `EmployeeField/Result/EmployeeFieldItemResult.php`, `EmployeeField/Result/EmployeeFieldResult.php`, + `EmployeeField/Result/EmployeeFieldsResult.php` +- `NodeField/Result/NodeFieldItemResult.php`, `NodeField/Result/NodeFieldResult.php`, + `NodeField/Result/NodeFieldsResult.php` +- `NodeMemberField/Result/NodeMemberFieldItemResult.php`, + `NodeMemberField/Result/NodeMemberFieldResult.php`, + `NodeMemberField/Result/NodeMemberFieldsResult.php` + +Generator commands to run before manual result-item edits: + +```bash +php bin/console b24-dev:result-item-generator humanresources.employee.search --stage=all +php bin/console b24-dev:result-item-generator humanresources.node.get --stage=all +php bin/console b24-dev:result-item-generator humanresources.node.member.field.get --stage=all +php bin/console b24-dev:result-item-generator humanresources.employee.field.get --stage=all +``` + +Generator status: + +- `docker compose run --rm -e BITRIX24_WEBHOOK="" php-cli php bin/console b24-dev:result-item-generator humanresources.employee.search --stage=all` + failed with `Unable to determine the current git branch`. +- The `php-cli` image does not contain `git`, and the generator fallback expects `.git/HEAD` + to exist as a directory path. This worktree uses the standard git-worktree `.git` file + pointing to the main repository metadata, so the fallback cannot resolve the branch. +- Result item classes are therefore created manually from the official API response + descriptions and will be verified by the mandatory annotation integration tests. +- Live portal verification note: the current test webhook portal has no `humanresources` + scope in `app.info` and returns `ERROR_METHOD_NOT_FOUND` for + `humanresources.employee.field.list`, `humanresources.node.field.list`, and + `humanresources.node.member.field.list`. Integration tests must therefore skip with an + explicit message on portals where this scope is not available. + +### 4. Unit tests + +Create: + +- `tests/Unit/Services/HumanResources/HumanResourcesServiceBuilderTest.php` +- `tests/Unit/Services/HumanResources/Service/EmployeeTest.php` +- `tests/Unit/Services/HumanResources/Service/EmployeeFieldTest.php` +- `tests/Unit/Services/HumanResources/Service/NodeTest.php` +- `tests/Unit/Services/HumanResources/Service/NodeFieldTest.php` +- `tests/Unit/Services/HumanResources/Service/NodeCommunicationTest.php` +- `tests/Unit/Services/HumanResources/Service/NodeMemberTest.php` +- `tests/Unit/Services/HumanResources/Service/NodeMemberFieldTest.php` + +Use TDD: write a failing test for each method before production code. +Each test must assert the exact REST method name, parameters, `ApiVersion::v3`, and result class. + +### 5. Integration tests + +Create: + +- `tests/Integration/Services/HumanResources/Service/EmployeeTest.php` +- `tests/Integration/Services/HumanResources/Service/EmployeeFieldTest.php` +- `tests/Integration/Services/HumanResources/Service/NodeTest.php` +- `tests/Integration/Services/HumanResources/Service/NodeFieldTest.php` +- `tests/Integration/Services/HumanResources/Service/NodeCommunicationTest.php` +- `tests/Integration/Services/HumanResources/Service/NodeMemberTest.php` +- `tests/Integration/Services/HumanResources/Service/NodeMemberFieldTest.php` +- `tests/Integration/Services/HumanResources/Result/EmployeeItemResultAnnotationsTest.php` +- `tests/Integration/Services/HumanResources/Result/NodeItemResultAnnotationsTest.php` +- `tests/Integration/Services/HumanResources/Result/NodeMemberItemResultAnnotationsTest.php` +- `tests/Integration/Services/HumanResources/Result/EmployeeFieldItemResultAnnotationsTest.php` +- `tests/Integration/Services/HumanResources/Result/NodeFieldItemResultAnnotationsTest.php` +- `tests/Integration/Services/HumanResources/Result/NodeMemberFieldItemResultAnnotationsTest.php` + +Integration tests must avoid destructive org-structure changes unless they create and clean up +their own test node. For mutation methods, prefer unit coverage first and keep live integration +coverage narrow enough to be repeatable on the shared test portal. + +## Files to Modify + +### 1. `src/Services/ServiceBuilder.php` + +Add import for `Bitrix24\SDK\Services\HumanResources\HumanResourcesServiceBuilder`. +Add `getHumanResourcesScope(): HumanResourcesServiceBuilder`. + +### 2. `phpunit.xml.dist` + +Add a suite: + +```xml + + ./tests/Integration/Services/HumanResources/ + +``` + +### 3. `Makefile` + +Add help line and target: + +```make +test-integration-scope-humanresources: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_humanresources +``` + +### 4. `CHANGELOG.md` + +Add under `## X.Y.Z Unreleased` -> `### Added`: + +```markdown +- Added v3 `humanresources.*` SDK services for company org structure, employees, + node communications, and node members ([#517](https://github.com/bitrix24/b24phpsdk/issues/517)) +``` + +## Deptrac compliance + +The new code stays inside the existing Services layer: + +- services depend on Core contracts, credentials scope, exceptions, result wrappers, attributes, and logger +- result classes depend only on Core result abstractions and value classes such as `CarbonImmutable` +- tests use existing Unit stubs and Integration `Factory` + +No dependency from Services to Tests, Infrastructure, or OpenApi runtime code is allowed. + +## Verification + +Run targeted RED/GREEN tests during implementation, then the full gate: + +```bash +make test-file FILE=tests/Unit/Services/HumanResources/Service/EmployeeTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/EmployeeFieldTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/NodeTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/NodeFieldTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/NodeCommunicationTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/NodeMemberTest.php +make test-file FILE=tests/Unit/Services/HumanResources/Service/NodeMemberFieldTest.php +make test-unit +make test-integration-scope-humanresources +make lint-cs-fixer +make lint-rector +make lint-phpstan +make lint-deptrac +make lint-all +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index ac030197..715264db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,9 +79,14 @@ - `getList` gets the list of widgets for the current application - `debug` enables or disables debug mode for all widgets of the current application - Added `repoWidget()` accessor to `LandingServiceBuilder` ([#501](https://github.com/bitrix24/b24phpsdk/issues/501)) +- Added `Services\HumanResources` v3 scope wrappers for company org structure, + employees, node communications, node members, and dedicated field metadata services + ([#517](https://github.com/bitrix24/b24phpsdk/issues/517)) ### Changed +- Updated `b24phpsdk-maintainer` skill: require dedicated field metadata services for + `*.field.get` and `*.field.list` endpoints ([#517](https://github.com/bitrix24/b24phpsdk/issues/517)) - Updated `b24phpsdk-maintainer` skill: `*ItemResult` classes must extend `Core\Result\AbstractAnnotatedItem` (auto-casts from `@property-read` annotations) instead of the legacy `AbstractItem` + manual `__get` pattern ([#518](https://github.com/bitrix24/b24phpsdk/issues/518)) diff --git a/Makefile b/Makefile index 53a12d42..36baa4f3 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,7 @@ help: @echo "test-integration-im-chat - run IM Chat integration tests" @echo "test-integration-im-chat-user - run IM Chat User integration tests" @echo "test-integration-im-notify - run IM Notify integration tests" + @echo "test-integration-scope-humanresources - run HumanResources integration tests" @echo "test-integration-scope-lists - run Lists integration tests" @echo "test-integration-lists-service - run Lists Service integration tests" @echo "test-integration-lists-field - run Lists Field integration tests" @@ -621,6 +622,9 @@ test-integration-rest-scope: test-integration-scope-timeman: docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_timeman +.PHONY: test-integration-scope-humanresources +test-integration-scope-humanresources: + @docker compose run --rm -e BITRIX24_WEBHOOK="$(BITRIX24_WEBHOOK)" php-cli $(PHPUNIT) --testsuite integration_tests_scope_humanresources .PHONY: test-integration-timeman-record test-integration-timeman-record: docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_timeman_record diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a0fbb94a..ca21f7f9 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -399,6 +399,8 @@ ./tests/Integration/Services/Timeman/ + + ./tests/Integration/Services/HumanResources/ ./tests/Integration/Services/Timeman/Record/Service/RecordTest.php ./tests/Integration/Services/Timeman/Record/Result/RecordItemResultTest.php diff --git a/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldItemResult.php b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldItemResult.php new file mode 100644 index 00000000..24d03adf --- /dev/null +++ b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldItemResult.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\HumanResources\EmployeeField\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read string $name + * @property-read string $type + * @property-read string $title + * @property-read string|null $description + * @property-read array|null $validationRules + * @property-read array|null $requiredGroups + * @property-read bool $filterable + * @property-read bool $sortable + * @property-read bool $editable + * @property-read bool $multiple + * @property-read string|null $elementType + */ +class EmployeeFieldItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldResult.php b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldResult.php new file mode 100644 index 00000000..03c8fc72 --- /dev/null +++ b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldResult.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\HumanResources\EmployeeField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class EmployeeFieldResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function employeeField(): EmployeeFieldItemResult + { + return new EmployeeFieldItemResult($this->getItemData()); + } +} diff --git a/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldsResult.php b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldsResult.php new file mode 100644 index 00000000..23ccefe4 --- /dev/null +++ b/src/Services/HumanResources/EmployeeField/Result/EmployeeFieldsResult.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\HumanResources\EmployeeField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class EmployeeFieldsResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return EmployeeFieldItemResult[] + * @throws BaseException + */ + public function getEmployeeFields(): array + { + return array_map( + static fn(array $item): EmployeeFieldItemResult => new EmployeeFieldItemResult($item), + $this->getItemsData() + ); + } + + /** + * @return array> + * @throws BaseException + */ + public function getFieldsDescription(): array + { + $fields = []; + foreach ($this->getItemsData() as $item) { + $fields[(string)$item['name']] = $item; + } + + return $fields; + } +} diff --git a/src/Services/HumanResources/EmployeeField/Service/EmployeeField.php b/src/Services/HumanResources/EmployeeField/Service/EmployeeField.php new file mode 100644 index 00000000..fe35b8a3 --- /dev/null +++ b/src/Services/HumanResources/EmployeeField/Service/EmployeeField.php @@ -0,0 +1,75 @@ + + * + * 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\HumanResources\EmployeeField\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\EmployeeField\Result\EmployeeFieldResult; +use Bitrix24\SDK\Services\HumanResources\EmployeeField\Result\EmployeeFieldsResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class EmployeeField extends AbstractService +{ + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.field.get', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-field-get.html', + 'Get employee field metadata by name', + ApiVersion::v3 + )] + public function get(string $name, array $select = []): EmployeeFieldResult + { + $this->guardNonEmptyString($name, 'field name must not be empty'); + + $params = ['name' => $name]; + if ($select !== []) { + $params['select'] = $select; + } + + return new EmployeeFieldResult( + $this->core->call('humanresources.employee.field.get', $params, ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.field.list', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-field-list.html', + 'Get employee field metadata list', + ApiVersion::v3 + )] + public function list(array $select = []): EmployeeFieldsResult + { + $params = $select !== [] ? ['select' => $select] : []; + + return new EmployeeFieldsResult( + $this->core->call('humanresources.employee.field.list', $params, ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/HumanResourcesServiceBuilder.php b/src/Services/HumanResources/HumanResourcesServiceBuilder.php new file mode 100644 index 00000000..413a3244 --- /dev/null +++ b/src/Services/HumanResources/HumanResourcesServiceBuilder.php @@ -0,0 +1,85 @@ + + * + * 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\HumanResources; + +use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Services\AbstractServiceBuilder; + +#[ApiServiceBuilderMetadata(new Scope(['humanresources']))] +class HumanResourcesServiceBuilder extends AbstractServiceBuilder +{ + public function employee(): Service\Employee + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Service\Employee($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function employeeField(): EmployeeField\Service\EmployeeField + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new EmployeeField\Service\EmployeeField($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function node(): Service\Node + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Service\Node($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function nodeField(): NodeField\Service\NodeField + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new NodeField\Service\NodeField($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function nodeCommunication(): Service\NodeCommunication + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Service\NodeCommunication($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function nodeMember(): Service\NodeMember + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Service\NodeMember($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function nodeMemberField(): NodeMemberField\Service\NodeMemberField + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new NodeMemberField\Service\NodeMemberField($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/HumanResources/NodeField/Result/NodeFieldItemResult.php b/src/Services/HumanResources/NodeField/Result/NodeFieldItemResult.php new file mode 100644 index 00000000..ce2c8658 --- /dev/null +++ b/src/Services/HumanResources/NodeField/Result/NodeFieldItemResult.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\HumanResources\NodeField\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read string $name + * @property-read string $type + * @property-read string $title + * @property-read string|null $description + * @property-read array|null $validationRules + * @property-read array|null $requiredGroups + * @property-read bool $filterable + * @property-read bool $sortable + * @property-read bool $editable + * @property-read bool $multiple + * @property-read string|null $elementType + */ +class NodeFieldItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/NodeField/Result/NodeFieldResult.php b/src/Services/HumanResources/NodeField/Result/NodeFieldResult.php new file mode 100644 index 00000000..a95e8839 --- /dev/null +++ b/src/Services/HumanResources/NodeField/Result/NodeFieldResult.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\HumanResources\NodeField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class NodeFieldResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function nodeField(): NodeFieldItemResult + { + return new NodeFieldItemResult($this->getItemData()); + } +} diff --git a/src/Services/HumanResources/NodeField/Result/NodeFieldsResult.php b/src/Services/HumanResources/NodeField/Result/NodeFieldsResult.php new file mode 100644 index 00000000..4de41490 --- /dev/null +++ b/src/Services/HumanResources/NodeField/Result/NodeFieldsResult.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\HumanResources\NodeField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class NodeFieldsResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return NodeFieldItemResult[] + * @throws BaseException + */ + public function getNodeFields(): array + { + return array_map( + static fn(array $item): NodeFieldItemResult => new NodeFieldItemResult($item), + $this->getItemsData() + ); + } + + /** + * @return array> + * @throws BaseException + */ + public function getFieldsDescription(): array + { + $fields = []; + foreach ($this->getItemsData() as $item) { + $fields[(string)$item['name']] = $item; + } + + return $fields; + } +} diff --git a/src/Services/HumanResources/NodeField/Service/NodeField.php b/src/Services/HumanResources/NodeField/Service/NodeField.php new file mode 100644 index 00000000..f85a9071 --- /dev/null +++ b/src/Services/HumanResources/NodeField/Service/NodeField.php @@ -0,0 +1,75 @@ + + * + * 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\HumanResources\NodeField\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\NodeField\Result\NodeFieldResult; +use Bitrix24\SDK\Services\HumanResources\NodeField\Result\NodeFieldsResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class NodeField extends AbstractService +{ + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.field.get', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-field-get.html', + 'Get org-structure node field metadata by name', + ApiVersion::v3 + )] + public function get(string $name, array $select = []): NodeFieldResult + { + $this->guardNonEmptyString($name, 'field name must not be empty'); + + $params = ['name' => $name]; + if ($select !== []) { + $params['select'] = $select; + } + + return new NodeFieldResult( + $this->core->call('humanresources.node.field.get', $params, ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.field.list', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-field-list.html', + 'Get org-structure node field metadata list', + ApiVersion::v3 + )] + public function list(array $select = []): NodeFieldsResult + { + $params = $select !== [] ? ['select' => $select] : []; + + return new NodeFieldsResult( + $this->core->call('humanresources.node.field.list', $params, ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldItemResult.php b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldItemResult.php new file mode 100644 index 00000000..e525496d --- /dev/null +++ b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldItemResult.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\HumanResources\NodeMemberField\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read string $name + * @property-read string $type + * @property-read string $title + * @property-read string|null $description + * @property-read array|null $validationRules + * @property-read array|null $requiredGroups + * @property-read bool $filterable + * @property-read bool $sortable + * @property-read bool $editable + * @property-read bool $multiple + * @property-read string|null $elementType + */ +class NodeMemberFieldItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldResult.php b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldResult.php new file mode 100644 index 00000000..0af1ba4b --- /dev/null +++ b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldResult.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\HumanResources\NodeMemberField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class NodeMemberFieldResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function nodeMemberField(): NodeMemberFieldItemResult + { + return new NodeMemberFieldItemResult($this->getItemData()); + } +} diff --git a/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldsResult.php b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldsResult.php new file mode 100644 index 00000000..a06db3aa --- /dev/null +++ b/src/Services/HumanResources/NodeMemberField/Result/NodeMemberFieldsResult.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\HumanResources\NodeMemberField\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\HumanResources\Result\ResultExtractor; + +class NodeMemberFieldsResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return NodeMemberFieldItemResult[] + * @throws BaseException + */ + public function getNodeMemberFields(): array + { + return array_map( + static fn(array $item): NodeMemberFieldItemResult => new NodeMemberFieldItemResult($item), + $this->getItemsData() + ); + } + + /** + * @return array> + * @throws BaseException + */ + public function getFieldsDescription(): array + { + $fields = []; + foreach ($this->getItemsData() as $item) { + $fields[(string)$item['name']] = $item; + } + + return $fields; + } +} diff --git a/src/Services/HumanResources/NodeMemberField/Service/NodeMemberField.php b/src/Services/HumanResources/NodeMemberField/Service/NodeMemberField.php new file mode 100644 index 00000000..3cfc8410 --- /dev/null +++ b/src/Services/HumanResources/NodeMemberField/Service/NodeMemberField.php @@ -0,0 +1,75 @@ + + * + * 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\HumanResources\NodeMemberField\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\NodeMemberField\Result\NodeMemberFieldResult; +use Bitrix24\SDK\Services\HumanResources\NodeMemberField\Result\NodeMemberFieldsResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class NodeMemberField extends AbstractService +{ + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.field.get', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-field-get.html', + 'Get node member field metadata by name', + ApiVersion::v3 + )] + public function get(string $name, array $select = []): NodeMemberFieldResult + { + $this->guardNonEmptyString($name, 'field name must not be empty'); + + $params = ['name' => $name]; + if ($select !== []) { + $params['select'] = $select; + } + + return new NodeMemberFieldResult( + $this->core->call('humanresources.node.member.field.get', $params, ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.field.list', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-field-list.html', + 'Get node member field metadata list', + ApiVersion::v3 + )] + public function list(array $select = []): NodeMemberFieldsResult + { + $params = $select !== [] ? ['select' => $select] : []; + + return new NodeMemberFieldsResult( + $this->core->call('humanresources.node.member.field.list', $params, ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/Result/EmployeeCountResult.php b/src/Services/HumanResources/Result/EmployeeCountResult.php new file mode 100644 index 00000000..32ad8044 --- /dev/null +++ b/src/Services/HumanResources/Result/EmployeeCountResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class EmployeeCountResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function getTotal(): int + { + return (int)($this->getResultData()['total'] ?? 0); + } +} diff --git a/src/Services/HumanResources/Result/EmployeeItemResult.php b/src/Services/HumanResources/Result/EmployeeItemResult.php new file mode 100644 index 00000000..201be17e --- /dev/null +++ b/src/Services/HumanResources/Result/EmployeeItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\HumanResources\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read int|null $userId + * @property-read string|null $name + * @property-read string|null $workPosition + * @property-read string|null $avatar + * @property-read string|null $url + * @property-read array|null $departments + * @property-read array|null $teams + */ +class EmployeeItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/Result/EmployeeMultidepartmentResult.php b/src/Services/HumanResources/Result/EmployeeMultidepartmentResult.php new file mode 100644 index 00000000..b2676897 --- /dev/null +++ b/src/Services/HumanResources/Result/EmployeeMultidepartmentResult.php @@ -0,0 +1,42 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class EmployeeMultidepartmentResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return EmployeeItemResult[] + * @throws BaseException + */ + public function getEmployees(): array + { + return array_map( + static fn(array $item): EmployeeItemResult => new EmployeeItemResult($item), + $this->getResultData()['employees'] ?? [] + ); + } + + /** + * @throws BaseException + */ + public function getTotal(): int + { + return (int)($this->getResultData()['total'] ?? 0); + } +} diff --git a/src/Services/HumanResources/Result/EmployeeSubordinatesResult.php b/src/Services/HumanResources/Result/EmployeeSubordinatesResult.php new file mode 100644 index 00000000..ab14b5bc --- /dev/null +++ b/src/Services/HumanResources/Result/EmployeeSubordinatesResult.php @@ -0,0 +1,39 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class EmployeeSubordinatesResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function getUserId(): int + { + return (int)($this->getResultData()['userId'] ?? 0); + } + + /** + * @return int[] + * @throws BaseException + */ + public function getDepartments(): array + { + return $this->getResultData()['departments'] ?? []; + } +} diff --git a/src/Services/HumanResources/Result/EmployeesResult.php b/src/Services/HumanResources/Result/EmployeesResult.php new file mode 100644 index 00000000..e99e0e74 --- /dev/null +++ b/src/Services/HumanResources/Result/EmployeesResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class EmployeesResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return EmployeeItemResult[] + * @throws BaseException + */ + public function getEmployees(): array + { + return array_map( + static fn(array $item): EmployeeItemResult => new EmployeeItemResult($item), + $this->getItemsData() + ); + } +} diff --git a/src/Services/HumanResources/Result/NodeCommunicationEditResult.php b/src/Services/HumanResources/Result/NodeCommunicationEditResult.php new file mode 100644 index 00000000..728febf1 --- /dev/null +++ b/src/Services/HumanResources/Result/NodeCommunicationEditResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeCommunicationEditResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getResultData()['success'] ?? false); + } +} diff --git a/src/Services/HumanResources/Result/NodeCommunicationResult.php b/src/Services/HumanResources/Result/NodeCommunicationResult.php new file mode 100644 index 00000000..f157d744 --- /dev/null +++ b/src/Services/HumanResources/Result/NodeCommunicationResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeCommunicationResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return array|array + * @throws BaseException + */ + public function getCommunications(): array + { + return $this->getResultData(); + } +} diff --git a/src/Services/HumanResources/Result/NodeCountResult.php b/src/Services/HumanResources/Result/NodeCountResult.php new file mode 100644 index 00000000..1bd6261b --- /dev/null +++ b/src/Services/HumanResources/Result/NodeCountResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeCountResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function getDepartments(): int + { + return (int)($this->getResultData()['departments'] ?? 0); + } + + /** + * @throws BaseException + */ + public function getTeams(): int + { + return (int)($this->getResultData()['teams'] ?? 0); + } +} diff --git a/src/Services/HumanResources/Result/NodeItemResult.php b/src/Services/HumanResources/Result/NodeItemResult.php new file mode 100644 index 00000000..ff5c617e --- /dev/null +++ b/src/Services/HumanResources/Result/NodeItemResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int|null $id + * @property-read string|null $name + * @property-read string|null $type + * @property-read int|null $structureId + * @property-read int|null $parentId + * @property-read string|null $description + * @property-read string|null $accessCode + * @property-read int|null $userCount + * @property-read string|null $colorName + * @property-read string|null $xmlId + * @property-read CarbonImmutable|null $createdAt + * @property-read CarbonImmutable|null $updatedAt + * @property-read array|null $members + */ +class NodeItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/Result/NodeMemberItemResult.php b/src/Services/HumanResources/Result/NodeMemberItemResult.php new file mode 100644 index 00000000..f33164b7 --- /dev/null +++ b/src/Services/HumanResources/Result/NodeMemberItemResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Result\AbstractAnnotatedItem; + +/** + * @property-read int|null $userId + * @property-read string|null $name + * @property-read string|null $workPosition + * @property-read string|null $role + * @property-read string|null $avatar + * @property-read string|null $url + */ +class NodeMemberItemResult extends AbstractAnnotatedItem +{ +} diff --git a/src/Services/HumanResources/Result/NodeMemberOperationResult.php b/src/Services/HumanResources/Result/NodeMemberOperationResult.php new file mode 100644 index 00000000..a8eb389e --- /dev/null +++ b/src/Services/HumanResources/Result/NodeMemberOperationResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeMemberOperationResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getResultData()['success'] ?? false); + } +} diff --git a/src/Services/HumanResources/Result/NodeMemberRemoveResult.php b/src/Services/HumanResources/Result/NodeMemberRemoveResult.php new file mode 100644 index 00000000..6325658f --- /dev/null +++ b/src/Services/HumanResources/Result/NodeMemberRemoveResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeMemberRemoveResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return int[] + * @throws BaseException + */ + public function getRemoved(): array + { + return $this->getResultData()['removed'] ?? []; + } + + /** + * @return array + * @throws BaseException + */ + public function getFailed(): array + { + return $this->getResultData()['failed'] ?? []; + } +} diff --git a/src/Services/HumanResources/Result/NodeResult.php b/src/Services/HumanResources/Result/NodeResult.php new file mode 100644 index 00000000..a16d357d --- /dev/null +++ b/src/Services/HumanResources/Result/NodeResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodeResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @throws BaseException + */ + public function getNode(): NodeItemResult + { + return new NodeItemResult($this->getItemData()); + } +} diff --git a/src/Services/HumanResources/Result/NodesResult.php b/src/Services/HumanResources/Result/NodesResult.php new file mode 100644 index 00000000..2c491f2a --- /dev/null +++ b/src/Services/HumanResources/Result/NodesResult.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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NodesResult extends AbstractResult +{ + use ResultExtractor; + + /** + * @return NodeItemResult[] + * @throws BaseException + */ + public function getNodes(): array + { + return array_map( + static fn(array $item): NodeItemResult => new NodeItemResult($item), + $this->getItemsData() + ); + } +} diff --git a/src/Services/HumanResources/Result/ResultExtractor.php b/src/Services/HumanResources/Result/ResultExtractor.php new file mode 100644 index 00000000..c0368f26 --- /dev/null +++ b/src/Services/HumanResources/Result/ResultExtractor.php @@ -0,0 +1,50 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; + +trait ResultExtractor +{ + /** + * @return array|array + * @throws BaseException + */ + private function getResultData(): array + { + return $this->getCoreResponse()->getResponseData()->getResult(); + } + + /** + * @return array + * @throws BaseException + */ + private function getItemData(): array + { + $result = $this->getResultData(); + + return $result['item'] ?? $result; + } + + /** + * @return array> + * @throws BaseException + */ + private function getItemsData(): array + { + $result = $this->getResultData(); + + return $result['items'] ?? $result; + } +} diff --git a/src/Services/HumanResources/Service/Employee.php b/src/Services/HumanResources/Service/Employee.php new file mode 100644 index 00000000..280b664c --- /dev/null +++ b/src/Services/HumanResources/Service/Employee.php @@ -0,0 +1,113 @@ + + * + * 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\HumanResources\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\Result\EmployeeCountResult; +use Bitrix24\SDK\Services\HumanResources\Result\EmployeeMultidepartmentResult; +use Bitrix24\SDK\Services\HumanResources\Result\EmployeesResult; +use Bitrix24\SDK\Services\HumanResources\Result\EmployeeSubordinatesResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class Employee extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.count', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-count.html', + 'Get company employee count', + ApiVersion::v3 + )] + public function count(): EmployeeCountResult + { + return new EmployeeCountResult( + $this->core->call('humanresources.employee.count', [], ApiVersion::v3) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.multidepartment', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-multidepartment.html', + 'Get employees assigned to several departments', + ApiVersion::v3 + )] + public function multidepartment(): EmployeeMultidepartmentResult + { + return new EmployeeMultidepartmentResult( + $this->core->call('humanresources.employee.multidepartment', [], ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.search', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-search.html', + 'Search employees by name', + ApiVersion::v3 + )] + public function search(string $name, ?int $nodeId = null, array $select = []): EmployeesResult + { + $this->guardNonEmptyString($name, 'employee name must not be empty'); + + $params = ['name' => $name]; + if ($nodeId !== null) { + $params['nodeId'] = $nodeId; + } + + if ($select !== []) { + $params['select'] = $select; + } + + return new EmployeesResult( + $this->core->call('humanresources.employee.search', $params, ApiVersion::v3) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.employee.subordinates', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/employee/humanresources-employee-subordinates.html', + 'Get employee subordinates', + ApiVersion::v3 + )] + public function subordinates(int $id): EmployeeSubordinatesResult + { + $this->guardPositiveId($id); + + return new EmployeeSubordinatesResult( + $this->core->call('humanresources.employee.subordinates', ['id' => $id], ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/Service/Node.php b/src/Services/HumanResources/Service/Node.php new file mode 100644 index 00000000..513b5b64 --- /dev/null +++ b/src/Services/HumanResources/Service/Node.php @@ -0,0 +1,228 @@ + + * + * 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\HumanResources\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\Result\NodeCountResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodeResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodesResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class Node extends AbstractService +{ + /** + * @param array $optional + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.add', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-add.html', + 'Add an org-structure node', + ApiVersion::v3 + )] + public function add(string $type, string $name, int $parentId, array $optional = []): NodeResult + { + $this->guardNonEmptyString($type, 'node type must not be empty'); + $this->guardNonEmptyString($name, 'node name must not be empty'); + $this->guardPositiveId($parentId); + + return new NodeResult( + $this->core->call( + 'humanresources.node.add', + array_merge(['type' => $type, 'name' => $name, 'parentId' => $parentId], $optional), + ApiVersion::v3 + ) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.children', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-children.html', + 'Get child org-structure nodes', + ApiVersion::v3 + )] + public function children(int $id, array $select = []): NodesResult + { + $this->guardPositiveId($id); + + $params = ['id' => $id]; + if ($select !== []) { + $params['select'] = $select; + } + + return new NodesResult( + $this->core->call('humanresources.node.children', $params, ApiVersion::v3) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.count', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-count.html', + 'Get org-structure node counters', + ApiVersion::v3 + )] + public function count(): NodeCountResult + { + return new NodeCountResult( + $this->core->call('humanresources.node.count', [], ApiVersion::v3) + ); + } + + /** + * @param array $fields + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.edit', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-edit.html', + 'Edit an org-structure node', + ApiVersion::v3 + )] + public function edit(int $id, array $fields): NodeResult + { + $this->guardPositiveId($id); + + return new NodeResult( + $this->core->call('humanresources.node.edit', array_merge(['id' => $id], $fields), ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.get', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-get.html', + 'Get an org-structure node by id', + ApiVersion::v3 + )] + public function get(int $id, array $select = []): NodeResult + { + $this->guardPositiveId($id); + + $params = ['id' => $id]; + if ($select !== []) { + $params['select'] = $select; + } + + return new NodeResult( + $this->core->call('humanresources.node.get', $params, ApiVersion::v3) + ); + } + + /** + * @param string[] $select + * @param array $pagination + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.list', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-list.html', + 'Get org-structure nodes', + ApiVersion::v3 + )] + public function list(string $type, array $select = [], array $pagination = []): NodesResult + { + $this->guardNonEmptyString($type, 'node type must not be empty'); + + $params = ['type' => $type]; + if ($select !== []) { + $params['select'] = $select; + } + + if ($pagination !== []) { + $params['pagination'] = $pagination; + } + + return new NodesResult( + $this->core->call('humanresources.node.list', $params, ApiVersion::v3) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.move', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-move.html', + 'Move an org-structure node', + ApiVersion::v3 + )] + public function move(int $id, int $parentId): NodeResult + { + $this->guardPositiveId($id); + $this->guardPositiveId($parentId); + + return new NodeResult( + $this->core->call('humanresources.node.move', ['id' => $id, 'parentId' => $parentId], ApiVersion::v3) + ); + } + + /** + * @param array $pagination + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.search', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node/humanresources-node-search.html', + 'Search org-structure nodes', + ApiVersion::v3 + )] + public function search(string $type, string $name, ?int $parentId = null, array $pagination = []): NodesResult + { + $this->guardNonEmptyString($type, 'node type must not be empty'); + $this->guardNonEmptyString($name, 'node name must not be empty'); + + $params = ['type' => $type, 'name' => $name]; + if ($parentId !== null) { + $params['parentId'] = $parentId; + } + + if ($pagination !== []) { + $params['pagination'] = $pagination; + } + + return new NodesResult( + $this->core->call('humanresources.node.search', $params, ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/Service/NodeCommunication.php b/src/Services/HumanResources/Service/NodeCommunication.php new file mode 100644 index 00000000..71537235 --- /dev/null +++ b/src/Services/HumanResources/Service/NodeCommunication.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\HumanResources\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\Result\NodeCommunicationEditResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodeCommunicationResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class NodeCommunication extends AbstractService +{ + /** + * @param array $options + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.communication.edit', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-communication/humanresources-node-communication-edit.html', + 'Edit node communications', + ApiVersion::v3 + )] + public function edit(int $nodeId, string $communicationType, array $options = []): NodeCommunicationEditResult + { + $this->guardPositiveId($nodeId); + $this->guardNonEmptyString($communicationType, 'communication type must not be empty'); + + return new NodeCommunicationEditResult( + $this->core->call( + 'humanresources.node.communication.edit', + array_merge(['nodeId' => $nodeId, 'communicationType' => $communicationType], $options), + ApiVersion::v3 + ) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.communication.list', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-communication/humanresources-node-communication-list.html', + 'Get node communications', + ApiVersion::v3 + )] + public function list(int $id): NodeCommunicationResult + { + $this->guardPositiveId($id); + + return new NodeCommunicationResult( + $this->core->call('humanresources.node.communication.list', ['id' => $id], ApiVersion::v3) + ); + } +} diff --git a/src/Services/HumanResources/Service/NodeMember.php b/src/Services/HumanResources/Service/NodeMember.php new file mode 100644 index 00000000..85975a01 --- /dev/null +++ b/src/Services/HumanResources/Service/NodeMember.php @@ -0,0 +1,131 @@ + + * + * 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\HumanResources\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\ApiVersion; +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\HumanResources\Result\NodeMemberOperationResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodeMemberRemoveResult; + +#[ApiServiceMetadata(new Scope(['humanresources']))] +class NodeMember extends AbstractService +{ + /** + * @param int[] $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.add', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-add.html', + 'Add node members', + ApiVersion::v3 + )] + public function add(int $nodeId, array $userIds, string $role): NodeMemberOperationResult + { + $this->guardPositiveId($nodeId); + $this->guardNonEmptyString($role, 'node member role must not be empty'); + + return new NodeMemberOperationResult( + $this->core->call( + 'humanresources.node.member.add', + ['nodeId' => $nodeId, 'userIds' => $userIds, 'role' => $role], + ApiVersion::v3 + ) + ); + } + + /** + * @param int[] $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.move', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-move.html', + 'Move node members', + ApiVersion::v3 + )] + public function move(int $nodeId, array $userIds, ?string $role = null): NodeMemberOperationResult + { + $this->guardPositiveId($nodeId); + + $params = ['nodeId' => $nodeId, 'userIds' => $userIds]; + if ($role !== null) { + $this->guardNonEmptyString($role, 'node member role must not be empty'); + $params['role'] = $role; + } + + return new NodeMemberOperationResult( + $this->core->call('humanresources.node.member.move', $params, ApiVersion::v3) + ); + } + + /** + * @param int[] $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.remove', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-remove.html', + 'Remove node members', + ApiVersion::v3 + )] + public function remove(int $nodeId, array $userIds): NodeMemberRemoveResult + { + $this->guardPositiveId($nodeId); + + return new NodeMemberRemoveResult( + $this->core->call( + 'humanresources.node.member.remove', + ['nodeId' => $nodeId, 'userIds' => $userIds], + ApiVersion::v3 + ) + ); + } + + /** + * @param array $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'humanresources.node.member.set', + 'https://apidocs.bitrix24.com/api-reference/rest-v3/humanresources/node-member/humanresources-node-member-set.html', + 'Set node members', + ApiVersion::v3 + )] + public function set(int $nodeId, array $userIds): NodeMemberOperationResult + { + $this->guardPositiveId($nodeId); + + return new NodeMemberOperationResult( + $this->core->call( + 'humanresources.node.member.set', + ['nodeId' => $nodeId, 'userIds' => $userIds], + ApiVersion::v3 + ) + ); + } +} diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php index 04cced9e..6683b5e4 100644 --- a/src/Services/ServiceBuilder.php +++ b/src/Services/ServiceBuilder.php @@ -24,6 +24,7 @@ use Bitrix24\SDK\Services\CRM\CRMServiceBuilder; use Bitrix24\SDK\Services\Disk\DiskServiceBuilder; use Bitrix24\SDK\Services\Entity\EntityServiceBuilder; +use Bitrix24\SDK\Services\HumanResources\HumanResourcesServiceBuilder; use Bitrix24\SDK\Services\Department\DepartmentServiceBuilder; use Bitrix24\SDK\Services\Task\TaskServiceBuilder; use Bitrix24\SDK\Services\IM\IMServiceBuilder; @@ -485,4 +486,18 @@ public function getTimemanScope(): TimemanServiceBuilder return $this->serviceCache[__METHOD__]; } + + public function getHumanResourcesScope(): HumanResourcesServiceBuilder + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new HumanResourcesServiceBuilder( + $this->core, + $this->batch, + $this->bulkItemsReader, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } } diff --git a/tests/Integration/Services/HumanResources/Result/AbstractHumanResourcesAnnotations.php b/tests/Integration/Services/HumanResources/Result/AbstractHumanResourcesAnnotations.php new file mode 100644 index 00000000..e7a65242 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/AbstractHumanResourcesAnnotations.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\HumanResources\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractItem; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use PHPUnit\Framework\TestCase; + +abstract class AbstractHumanResourcesAnnotations extends TestCase +{ + use CustomBitrix24Assertions; + + /** + * @template T + * @param callable(): T $callback + * @return T + * @throws BaseException + */ + protected function callOrSkipIfHumanResourcesUnavailable(callable $callback): mixed + { + try { + return $callback(); + } catch (BaseException $exception) { + if (str_contains($exception->getMessage(), 'ERROR_METHOD_NOT_FOUND') + || str_contains($exception->getMessage(), 'Method not found') + || str_contains($exception->getMessage(), 'insufficientscopeexception') + || str_contains($exception->getMessage(), 'отсутствует необходимый scope')) { + self::markTestSkipped('Portal does not support humanresources REST API methods.'); + } + + throw $exception; + } + } + + /** + * @param class-string $resultItemClassName + * @param array> $fieldsDescription + */ + protected function assertHumanResourcesFieldsAnnotated(array $fieldsDescription, string $resultItemClassName): void + { + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($fieldsDescription), $resultItemClassName); + } + + /** + * @param class-string $resultItemClassName + * @param array> $fieldsDescription + */ + protected function assertHumanResourcesFieldsHaveValidTypeAnnotations( + array $fieldsDescription, + string $resultItemClassName + ): void { + $fieldTypes = []; + foreach ($fieldsDescription as $fieldCode => $fieldData) { + $fieldTypes[$fieldCode] = [ + 'type' => $this->normalizeFieldType((string)$fieldCode, (string)($fieldData['type'] ?? 'string')), + ]; + } + + $this->assertBitrix24AllResultItemFieldsHasValidTypeAnnotation($fieldTypes, $resultItemClassName); + } + + /** + * @param class-string $resultItemClassName + */ + protected function assertFieldDescriptorItemAnnotations(array $rawItem, AbstractItem $item, string $resultItemClassName): void + { + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), $resultItemClassName); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($item, $resultItemClassName); + } + + private function normalizeFieldType(string $fieldCode, string $type): string + { + if ($type === 'boolean') { + return 'char'; + } + + if (in_array($fieldCode, ['createdAt', 'updatedAt'], true)) { + return 'datetime'; + } + + return $type; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/EmployeeFieldItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/EmployeeFieldItemResultAnnotationsTest.php new file mode 100644 index 00000000..8dc6f6f2 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/EmployeeFieldItemResultAnnotationsTest.php @@ -0,0 +1,64 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\EmployeeField\Result\EmployeeFieldItemResult; +use Bitrix24\SDK\Services\HumanResources\EmployeeField\Service\EmployeeField; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(EmployeeFieldItemResult::class)] +class EmployeeFieldItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private EmployeeField $employeeFieldService; + + #[\Override] + protected function setUp(): void + { + $this->employeeFieldService = Factory::getServiceBuilder()->getHumanResourcesScope()->employeeField(); + } + + #[Test] + #[TestDox('all system fields in EmployeeFieldItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getRawFieldItem()); + + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), EmployeeFieldItemResult::class); + } + + #[Test] + #[TestDox('all system fields in EmployeeFieldItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fieldItem = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): EmployeeFieldItemResult => $this->employeeFieldService->list()->getEmployeeFields()[0] + ); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($fieldItem, EmployeeFieldItemResult::class); + } + + /** + * @return array + */ + private function getRawFieldItem(): array + { + $result = $this->employeeFieldService->list()->getCoreResponse()->getResponseData()->getResult(); + $items = $result['items'] ?? $result; + + return $items[0]; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/EmployeeItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/EmployeeItemResultAnnotationsTest.php new file mode 100644 index 00000000..03c8d1a1 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/EmployeeItemResultAnnotationsTest.php @@ -0,0 +1,124 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\EmployeeField\Service\EmployeeField; +use Bitrix24\SDK\Services\HumanResources\Result\EmployeeItemResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodeItemResult; +use Bitrix24\SDK\Services\HumanResources\Service\Employee; +use Bitrix24\SDK\Services\HumanResources\Service\Node; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(EmployeeItemResult::class)] +class EmployeeItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private Employee $employeeService; + + private EmployeeField $employeeFieldService; + + private Node $nodeService; + + #[\Override] + protected function setUp(): void + { + $humanResourcesScope = Factory::getServiceBuilder()->getHumanResourcesScope(); + $this->employeeService = $humanResourcesScope->employee(); + $this->employeeFieldService = $humanResourcesScope->employeeField(); + $this->nodeService = $humanResourcesScope->node(); + } + + #[Test] + #[TestDox('all system fields in EmployeeItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->employeeFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsAnnotated($fields, EmployeeItemResult::class); + + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getSampleEmployeeRawItem()); + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), EmployeeItemResult::class); + } + + #[Test] + #[TestDox('all system fields in EmployeeItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->employeeFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsHaveValidTypeAnnotations($fields, EmployeeItemResult::class); + + $employeeItemResult = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): EmployeeItemResult => $this->getSampleEmployeeItem() + ); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($employeeItemResult, EmployeeItemResult::class); + } + + /** + * @return array + */ + private function getSampleEmployeeRawItem(): array + { + $employeeSearchResult = $this->employeeService->search( + $this->getSampleEmployeeName(), + null, + array_keys($this->employeeFieldService->list()->getFieldsDescription()) + ); + + $items = $employeeSearchResult->getCoreResponse()->getResponseData()->getResult()['items'] ?? []; + if ($items === []) { + self::markTestSkipped('No humanresources employee.search items available to validate annotations.'); + } + + return $items[0]; + } + + private function getSampleEmployeeItem(): EmployeeItemResult + { + $employees = $this->employeeService->search( + $this->getSampleEmployeeName(), + null, + array_keys($this->employeeFieldService->list()->getFieldsDescription()) + )->getEmployees(); + + if ($employees === []) { + self::markTestSkipped('No humanresources employee.search items available to validate type casting.'); + } + + return $employees[0]; + } + + private function getSampleEmployeeName(): string + { + $node = $this->nodeService->list('DEPARTMENT', ['id'], ['limit' => 10])->getNodes()[0] ?? null; + if (!$node instanceof NodeItemResult) { + self::markTestSkipped('No humanresources nodes available to resolve a sample employee name.'); + } + + $nodeResult = $this->nodeService->get((int)$node->id, ['members']); + $rawNode = $nodeResult->getCoreResponse()->getResponseData()->getResult()['item'] ?? []; + $memberName = $rawNode['members'][0]['name'] ?? null; + if (!is_string($memberName) || $memberName === '') { + self::markTestSkipped('No humanresources node members available to resolve a sample employee name.'); + } + + return $memberName; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/NodeFieldItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/NodeFieldItemResultAnnotationsTest.php new file mode 100644 index 00000000..25404420 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/NodeFieldItemResultAnnotationsTest.php @@ -0,0 +1,64 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\NodeField\Result\NodeFieldItemResult; +use Bitrix24\SDK\Services\HumanResources\NodeField\Service\NodeField; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(NodeFieldItemResult::class)] +class NodeFieldItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private NodeField $nodeFieldService; + + #[\Override] + protected function setUp(): void + { + $this->nodeFieldService = Factory::getServiceBuilder()->getHumanResourcesScope()->nodeField(); + } + + #[Test] + #[TestDox('all system fields in NodeFieldItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getRawFieldItem()); + + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), NodeFieldItemResult::class); + } + + #[Test] + #[TestDox('all system fields in NodeFieldItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fieldItem = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): NodeFieldItemResult => $this->nodeFieldService->list()->getNodeFields()[0] + ); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($fieldItem, NodeFieldItemResult::class); + } + + /** + * @return array + */ + private function getRawFieldItem(): array + { + $result = $this->nodeFieldService->list()->getCoreResponse()->getResponseData()->getResult(); + $items = $result['items'] ?? $result; + + return $items[0]; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/NodeItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/NodeItemResultAnnotationsTest.php new file mode 100644 index 00000000..6fd0b9c3 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/NodeItemResultAnnotationsTest.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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\NodeField\Service\NodeField; +use Bitrix24\SDK\Services\HumanResources\Result\NodeItemResult; +use Bitrix24\SDK\Services\HumanResources\Service\Node; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(NodeItemResult::class)] +class NodeItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private Node $nodeService; + + private NodeField $nodeFieldService; + + #[\Override] + protected function setUp(): void + { + $humanResourcesScope = Factory::getServiceBuilder()->getHumanResourcesScope(); + $this->nodeService = $humanResourcesScope->node(); + $this->nodeFieldService = $humanResourcesScope->nodeField(); + } + + #[Test] + #[TestDox('all system fields in NodeItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->nodeFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsAnnotated($fields, NodeItemResult::class); + + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getSampleNodeRawItem()); + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), NodeItemResult::class); + } + + #[Test] + #[TestDox('all system fields in NodeItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->nodeFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsHaveValidTypeAnnotations($fields, NodeItemResult::class); + + $nodeItemResult = $this->callOrSkipIfHumanResourcesUnavailable(fn(): NodeItemResult => $this->getSampleNodeItem()); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($nodeItemResult, NodeItemResult::class); + } + + /** + * @return array + */ + private function getSampleNodeRawItem(): array + { + $nodeResult = $this->nodeService->get( + $this->getSampleNodeId(), + array_keys($this->nodeFieldService->list()->getFieldsDescription()) + ); + + return $nodeResult->getCoreResponse()->getResponseData()->getResult()['item'] ?? []; + } + + private function getSampleNodeItem(): NodeItemResult + { + return $this->nodeService->get( + $this->getSampleNodeId(), + array_keys($this->nodeFieldService->list()->getFieldsDescription()) + )->getNode(); + } + + private function getSampleNodeId(): int + { + $nodes = $this->nodeService->list('DEPARTMENT', ['id'], ['limit' => 1])->getNodes(); + if ($nodes === []) { + self::markTestSkipped('No humanresources nodes available to validate annotations.'); + } + + return (int)$nodes[0]->id; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/NodeMemberFieldItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/NodeMemberFieldItemResultAnnotationsTest.php new file mode 100644 index 00000000..0001dfc8 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/NodeMemberFieldItemResultAnnotationsTest.php @@ -0,0 +1,64 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\NodeMemberField\Result\NodeMemberFieldItemResult; +use Bitrix24\SDK\Services\HumanResources\NodeMemberField\Service\NodeMemberField; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(NodeMemberFieldItemResult::class)] +class NodeMemberFieldItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private NodeMemberField $nodeMemberFieldService; + + #[\Override] + protected function setUp(): void + { + $this->nodeMemberFieldService = Factory::getServiceBuilder()->getHumanResourcesScope()->nodeMemberField(); + } + + #[Test] + #[TestDox('all system fields in NodeMemberFieldItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getRawFieldItem()); + + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), NodeMemberFieldItemResult::class); + } + + #[Test] + #[TestDox('all system fields in NodeMemberFieldItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fieldItem = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): NodeMemberFieldItemResult => $this->nodeMemberFieldService->list()->getNodeMemberFields()[0] + ); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations($fieldItem, NodeMemberFieldItemResult::class); + } + + /** + * @return array + */ + private function getRawFieldItem(): array + { + $result = $this->nodeMemberFieldService->list()->getCoreResponse()->getResponseData()->getResult(); + $items = $result['items'] ?? $result; + + return $items[0]; + } +} diff --git a/tests/Integration/Services/HumanResources/Result/NodeMemberItemResultAnnotationsTest.php b/tests/Integration/Services/HumanResources/Result/NodeMemberItemResultAnnotationsTest.php new file mode 100644 index 00000000..7955dba5 --- /dev/null +++ b/tests/Integration/Services/HumanResources/Result/NodeMemberItemResultAnnotationsTest.php @@ -0,0 +1,92 @@ + + * + * 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\HumanResources\Result; + +use Bitrix24\SDK\Services\HumanResources\NodeMemberField\Service\NodeMemberField; +use Bitrix24\SDK\Services\HumanResources\Result\NodeItemResult; +use Bitrix24\SDK\Services\HumanResources\Result\NodeMemberItemResult; +use Bitrix24\SDK\Services\HumanResources\Service\Node; +use Bitrix24\SDK\Tests\Integration\Factory; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(NodeMemberItemResult::class)] +class NodeMemberItemResultAnnotationsTest extends AbstractHumanResourcesAnnotations +{ + private Node $nodeService; + + private NodeMemberField $nodeMemberFieldService; + + #[\Override] + protected function setUp(): void + { + $humanResourcesScope = Factory::getServiceBuilder()->getHumanResourcesScope(); + $this->nodeService = $humanResourcesScope->node(); + $this->nodeMemberFieldService = $humanResourcesScope->nodeMemberField(); + } + + #[Test] + #[TestDox('all system fields in NodeMemberItemResult are annotated')] + public function testAllSystemFieldsAnnotated(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->nodeMemberFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsAnnotated($fields, NodeMemberItemResult::class); + + $rawItem = $this->callOrSkipIfHumanResourcesUnavailable(fn(): array => $this->getSampleNodeMemberRawItem()); + $this->assertBitrix24AllResultItemFieldsAnnotated(array_keys($rawItem), NodeMemberItemResult::class); + } + + #[Test] + #[TestDox('all system fields in NodeMemberItemResult have valid type annotations')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $fields = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): array => $this->nodeMemberFieldService->list()->getFieldsDescription() + ); + + $this->assertHumanResourcesFieldsHaveValidTypeAnnotations($fields, NodeMemberItemResult::class); + + $nodeMemberItemResult = $this->callOrSkipIfHumanResourcesUnavailable( + fn(): NodeMemberItemResult => new NodeMemberItemResult($this->getSampleNodeMemberRawItem()) + ); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $nodeMemberItemResult, + NodeMemberItemResult::class + ); + } + + /** + * @return array + */ + private function getSampleNodeMemberRawItem(): array + { + $node = $this->nodeService->list('DEPARTMENT', ['id'], ['limit' => 10])->getNodes()[0] ?? null; + if (!$node instanceof NodeItemResult) { + self::markTestSkipped('No humanresources nodes available to validate node members.'); + } + + $nodeResult = $this->nodeService->get((int)$node->id, ['members']); + $rawNode = $nodeResult->getCoreResponse()->getResponseData()->getResult()['item'] ?? []; + $rawMember = $rawNode['members'][0] ?? null; + if (!is_array($rawMember)) { + self::markTestSkipped('No humanresources node members available to validate annotations.'); + } + + return $rawMember; + } +} diff --git a/tests/Unit/Services/HumanResources/HumanResourcesServiceBuilderTest.php b/tests/Unit/Services/HumanResources/HumanResourcesServiceBuilderTest.php new file mode 100644 index 00000000..3ef8acb0 --- /dev/null +++ b/tests/Unit/Services/HumanResources/HumanResourcesServiceBuilderTest.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\Tests\Unit\Services\HumanResources; + +use Bitrix24\SDK\Services\HumanResources\HumanResourcesServiceBuilder; +use Bitrix24\SDK\Tests\Unit\Stubs\NullBatch; +use Bitrix24\SDK\Tests\Unit\Stubs\NullBulkItemsReader; +use Bitrix24\SDK\Tests\Unit\Stubs\NullCore; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; + +#[CoversClass(HumanResourcesServiceBuilder::class)] +class HumanResourcesServiceBuilderTest extends TestCase +{ + private HumanResourcesServiceBuilder $serviceBuilder; + + #[\Override] + protected function setUp(): void + { + $this->serviceBuilder = new HumanResourcesServiceBuilder( + new NullCore(), + new NullBatch(), + new NullBulkItemsReader(), + new NullLogger() + ); + } + + public function testEmployeeServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->employee(), $this->serviceBuilder->employee()); + } + + public function testEmployeeFieldServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->employeeField(), $this->serviceBuilder->employeeField()); + } + + public function testNodeServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->node(), $this->serviceBuilder->node()); + } + + public function testNodeFieldServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->nodeField(), $this->serviceBuilder->nodeField()); + } + + public function testNodeCommunicationServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->nodeCommunication(), $this->serviceBuilder->nodeCommunication()); + } + + public function testNodeMemberServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->nodeMember(), $this->serviceBuilder->nodeMember()); + } + + public function testNodeMemberFieldServiceIsCached(): void + { + self::assertSame($this->serviceBuilder->nodeMemberField(), $this->serviceBuilder->nodeMemberField()); + } +} diff --git a/tests/Unit/Services/HumanResources/Service/EmployeeFieldTest.php b/tests/Unit/Services/HumanResources/Service/EmployeeFieldTest.php new file mode 100644 index 00000000..2eb855d3 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/EmployeeFieldTest.php @@ -0,0 +1,53 @@ +mockCore('humanresources.employee.field.get', ['name' => 'userId', 'select' => ['name', 'type']]); + + self::assertInstanceOf(EmployeeFieldResult::class, (new EmployeeField($core, new NullLogger()))->get('userId', ['name', 'type'])); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('humanresources.employee.field.list', ['select' => ['name', 'type']]); + + self::assertInstanceOf(EmployeeFieldsResult::class, (new EmployeeField($core, new NullLogger()))->list(['name', 'type'])); + } + + public function testGetThrowsOnEmptyName(): void + { + $this->expectException(InvalidArgumentException::class); + + (new EmployeeField($this->createStub(CoreInterface::class), new NullLogger()))->get(''); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/EmployeeTest.php b/tests/Unit/Services/HumanResources/Service/EmployeeTest.php new file mode 100644 index 00000000..cd3bb794 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/EmployeeTest.php @@ -0,0 +1,78 @@ +mockCore('humanresources.employee.count', [], ApiVersion::v3); + + self::assertInstanceOf(EmployeeCountResult::class, (new Employee($core, new NullLogger()))->count()); + } + + public function testMultidepartmentCallsV3Method(): void + { + $core = $this->mockCore('humanresources.employee.multidepartment', [], ApiVersion::v3); + + self::assertInstanceOf( + EmployeeMultidepartmentResult::class, + (new Employee($core, new NullLogger()))->multidepartment() + ); + } + + public function testSearchBuildsRequiredAndOptionalParameters(): void + { + $core = $this->mockCore( + 'humanresources.employee.search', + [ + 'name' => 'Ivan', + 'nodeId' => 42, + 'select' => ['userId', 'name'], + ], + ApiVersion::v3 + ); + + self::assertInstanceOf( + EmployeesResult::class, + (new Employee($core, new NullLogger()))->search('Ivan', 42, ['userId', 'name']) + ); + } + + public function testSubordinatesCallsV3Method(): void + { + $core = $this->mockCore('humanresources.employee.subordinates', ['id' => 7], ApiVersion::v3); + + self::assertInstanceOf( + EmployeeSubordinatesResult::class, + (new Employee($core, new NullLogger()))->subordinates(7) + ); + } + + private function mockCore(string $method, array $parameters, ApiVersion $apiVersion): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, $apiVersion) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/NodeCommunicationTest.php b/tests/Unit/Services/HumanResources/Service/NodeCommunicationTest.php new file mode 100644 index 00000000..2fef2791 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/NodeCommunicationTest.php @@ -0,0 +1,59 @@ +mockCore( + 'humanresources.node.communication.edit', + [ + 'nodeId' => 15, + 'communicationType' => 'CHAT', + 'ids' => [21], + 'removeIds' => [18], + ] + ); + + self::assertInstanceOf( + NodeCommunicationEditResult::class, + (new NodeCommunication($core, new NullLogger()))->edit(15, 'CHAT', [ + 'ids' => [21], + 'removeIds' => [18], + ]) + ); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.communication.list', ['id' => 15]); + + self::assertInstanceOf(NodeCommunicationResult::class, (new NodeCommunication($core, new NullLogger()))->list(15)); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/NodeFieldTest.php b/tests/Unit/Services/HumanResources/Service/NodeFieldTest.php new file mode 100644 index 00000000..60d61b99 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/NodeFieldTest.php @@ -0,0 +1,53 @@ +mockCore('humanresources.node.field.get', ['name' => 'name', 'select' => ['name', 'type']]); + + self::assertInstanceOf(NodeFieldResult::class, (new NodeField($core, new NullLogger()))->get('name', ['name', 'type'])); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.field.list', ['select' => ['name', 'type']]); + + self::assertInstanceOf(NodeFieldsResult::class, (new NodeField($core, new NullLogger()))->list(['name', 'type'])); + } + + public function testGetThrowsOnEmptyName(): void + { + $this->expectException(InvalidArgumentException::class); + + (new NodeField($this->createStub(CoreInterface::class), new NullLogger()))->get(''); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/NodeMemberFieldTest.php b/tests/Unit/Services/HumanResources/Service/NodeMemberFieldTest.php new file mode 100644 index 00000000..5762b844 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/NodeMemberFieldTest.php @@ -0,0 +1,53 @@ +mockCore('humanresources.node.member.field.get', ['name' => 'userId', 'select' => ['name', 'type']]); + + self::assertInstanceOf(NodeMemberFieldResult::class, (new NodeMemberField($core, new NullLogger()))->get('userId', ['name', 'type'])); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.member.field.list', ['select' => ['name', 'type']]); + + self::assertInstanceOf(NodeMemberFieldsResult::class, (new NodeMemberField($core, new NullLogger()))->list(['name', 'type'])); + } + + public function testGetThrowsOnEmptyName(): void + { + $this->expectException(InvalidArgumentException::class); + + (new NodeMemberField($this->createStub(CoreInterface::class), new NullLogger()))->get(''); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/NodeMemberTest.php b/tests/Unit/Services/HumanResources/Service/NodeMemberTest.php new file mode 100644 index 00000000..a2d629f8 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/NodeMemberTest.php @@ -0,0 +1,88 @@ +mockCore('humanresources.node.member.add', [ + 'nodeId' => 15, + 'userIds' => [7, 12], + 'role' => 'MEMBER_EMPLOYEE', + ]); + + self::assertInstanceOf( + NodeMemberOperationResult::class, + (new NodeMember($core, new NullLogger()))->add(15, [7, 12], 'MEMBER_EMPLOYEE') + ); + } + + public function testMoveBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.member.move', [ + 'nodeId' => 28, + 'userIds' => [12, 18], + 'role' => 'MEMBER_EMPLOYEE', + ]); + + self::assertInstanceOf( + NodeMemberOperationResult::class, + (new NodeMember($core, new NullLogger()))->move(28, [12, 18], 'MEMBER_EMPLOYEE') + ); + } + + public function testRemoveBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.member.remove', [ + 'nodeId' => 15, + 'userIds' => [18, 25], + ]); + + self::assertInstanceOf(NodeMemberRemoveResult::class, (new NodeMember($core, new NullLogger()))->remove(15, [18, 25])); + } + + public function testSetBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.member.set', [ + 'nodeId' => 15, + 'userIds' => [ + 'MEMBER_HEAD' => [7], + 'MEMBER_EMPLOYEE' => [18, 25], + ], + ]); + + self::assertInstanceOf( + NodeMemberOperationResult::class, + (new NodeMember($core, new NullLogger()))->set(15, [ + 'MEMBER_HEAD' => [7], + 'MEMBER_EMPLOYEE' => [18, 25], + ]) + ); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +} diff --git a/tests/Unit/Services/HumanResources/Service/NodeTest.php b/tests/Unit/Services/HumanResources/Service/NodeTest.php new file mode 100644 index 00000000..e9ab7a64 --- /dev/null +++ b/tests/Unit/Services/HumanResources/Service/NodeTest.php @@ -0,0 +1,122 @@ +mockCore( + 'humanresources.node.add', + [ + 'type' => 'DEPARTMENT', + 'name' => 'Marketing', + 'parentId' => 1, + 'description' => 'Handles promotion', + ] + ); + + self::assertInstanceOf( + NodeResult::class, + (new Node($core, new NullLogger()))->add('DEPARTMENT', 'Marketing', 1, [ + 'description' => 'Handles promotion', + ]) + ); + } + + public function testChildrenBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.children', ['id' => 1, 'select' => ['id', 'name']]); + + self::assertInstanceOf(NodesResult::class, (new Node($core, new NullLogger()))->children(1, ['id', 'name'])); + } + + public function testCountCallsV3Method(): void + { + $core = $this->mockCore('humanresources.node.count', []); + + self::assertInstanceOf(NodeCountResult::class, (new Node($core, new NullLogger()))->count()); + } + + public function testEditBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.edit', ['id' => 44, 'name' => 'B2B Sales']); + + self::assertInstanceOf(NodeResult::class, (new Node($core, new NullLogger()))->edit(44, ['name' => 'B2B Sales'])); + } + + public function testGetBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.get', ['id' => 1, 'select' => ['id', 'name']]); + + self::assertInstanceOf(NodeResult::class, (new Node($core, new NullLogger()))->get(1, ['id', 'name'])); + } + + public function testListBuildsParameters(): void + { + $core = $this->mockCore( + 'humanresources.node.list', + [ + 'type' => 'DEPARTMENT', + 'select' => ['id', 'name'], + 'pagination' => ['limit' => 20], + ] + ); + + self::assertInstanceOf( + NodesResult::class, + (new Node($core, new NullLogger()))->list('DEPARTMENT', ['id', 'name'], ['limit' => 20]) + ); + } + + public function testMoveBuildsParameters(): void + { + $core = $this->mockCore('humanresources.node.move', ['id' => 44, 'parentId' => 2]); + + self::assertInstanceOf(NodeResult::class, (new Node($core, new NullLogger()))->move(44, 2)); + } + + public function testSearchBuildsParameters(): void + { + $core = $this->mockCore( + 'humanresources.node.search', + [ + 'type' => 'DEPARTMENT', + 'name' => 'Sales', + 'parentId' => 1, + 'pagination' => ['limit' => 20], + ] + ); + + self::assertInstanceOf( + NodesResult::class, + (new Node($core, new NullLogger()))->search('DEPARTMENT', 'Sales', 1, ['limit' => 20]) + ); + } + + private function mockCore(string $method, array $parameters): CoreInterface + { + $response = $this->createStub(Response::class); + $core = $this->createMock(CoreInterface::class); + $core->expects($this->once()) + ->method('call') + ->with($method, $parameters, ApiVersion::v3) + ->willReturn($response); + + return $core; + } +}