diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index bd422ca..7df57ce 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,3 +1,3 @@
-github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: # Replace with a single Patreon username
-custom: ['https://yoomoney.ru/to/41001685237530']
+github: anthonyaxenov
+patreon: anthonyaxenov
+custom: [ 'https://yoomoney.ru/to/41001685237530' ]
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..04162f3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,49 @@
+# https://github.com/shivammathur/setup-php/blob/master/README.md
+
+name: CI
+
+on:
+ push:
+ branches: [ master, dev ]
+ pull_request:
+ branches: [ dev ]
+
+jobs:
+ Tests:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php-version:
+ - 8.0
+ - 8.1
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v2
+ with:
+ path: ~/.composer/cache
+ key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }}
+ restore-keys: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-
+
+ - name: Setup PHP v ${{ matrix.php-version }}
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ extensions: json, curl, tokenizer, mbstring, xdebug
+ coverage: xdebug
+ tools: composer, phpunit
+
+ - name: Install dependencies with composer
+ run: composer install --no-ansi --no-interaction --no-progress
+
+ - name: Run tests with phpunit/phpunit
+ run: vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml
+
+ - name: Send code coverage report to Codecov.io
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ run: bash <(curl -s https://codecov.io/bash) || true
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
deleted file mode 100644
index 5bdc0de..0000000
--- a/.github/workflows/dev.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Dev build
-
-on:
- push:
- branches: [ dev ]
- pull_request:
- branches: [ dev ]
-
-jobs:
- Tests:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Validate composer.json and composer.lock
- uses: php-actions/composer@40-env
- with:
- version: 2
- php_version: 7.4
- only_args: --prefer-dist --no-progress
-
- - name: Run phpunit tests
- uses: php-actions/phpunit@v8
- with:
- configuration: ./phpunit.xml
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
deleted file mode 100644
index 0e0fd58..0000000
--- a/.github/workflows/master.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Master build
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-jobs:
- Tests:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Validate composer.json and composer.lock
- uses: php-actions/composer@40-env
- with:
- version: 2
- php_version: 7.4
- only_args: --prefer-dist --no-progress
-
- - name: Run phpunit tests
- uses: php-actions/phpunit@v8
- with:
- configuration: ./phpunit.xml
diff --git a/.gitignore b/.gitignore
index 5bc0a5c..6ccbbdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
/config.php
*cache*
/test.php
+/.coverage-report
diff --git a/README.md b/README.md
index 5043c89..b29805b 100644
--- a/README.md
+++ b/README.md
@@ -1,115 +1,91 @@
# АТОЛ Онлайн
-[![Master build](https://github.com/anthonyaxenov/atol-online/actions/workflows/master.yml/badge.svg)](https://github.com/anthonyaxenov/atol-online/actions/workflows/master.yml)
-[![Dev build](https://github.com/anthonyaxenov/atol-online/actions/workflows/dev.yml/badge.svg)](https://github.com/anthonyaxenov/atol-online/actions/workflows/dev.yml)
+[![Latest Stable Version](http://poser.pugx.org/axenov/atol-online/v)](https://packagist.org/packages/axenov/atol-online)
+[![Latest Unstable Version](http://poser.pugx.org/axenov/atol-online/v/unstable)](https://packagist.org/packages/axenov/atol-online)
+[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/axenov/atol-online)](https://packagist.org/packages/axenov/atol-online)
+[![Total Downloads](https://img.shields.io/packagist/dt/axenov/atol-online)](https://packagist.org/packages/axenov/atol-online)
+[![License](https://img.shields.io/packagist/l/axenov/atol-online?color=%23369883)](LICENSE)
-Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/).
-
-Текущая поддерживаемая версия API: **5.1**
+Библиотека для фискализации чеков по 54-ФЗ через [облачные ККТ АТОЛ](https://online.atol.ru/).
**[Документация](/docs/readme.md)**
+Текущие поддерживаемые версии АТОЛ Онлайн:
+
+| Протокол | API | ФФД | Статус |
+|----------|-----|------|----------------|
+| v4 | 5.8 | 1.05 | Поддерживается |
+| v5 | 2.0 | 1.2 | В планах |
+
+Состояние веток:
+
+| master | [![GitHub Workflow Status (master)](https://img.shields.io/github/workflow/status/anthonyaxenov/atol-online/CI/master?logo=github)](https://github.com/anthonyaxenov/atol-online/actions/workflows/ci.yml) | [![codecov](https://codecov.io/gh/anthonyaxenov/atol-online/branch/master/graph/badge.svg?token=WR2IV7FTF0)](https://codecov.io/gh/anthonyaxenov/atol-online) |
+|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| dev | [![GitHub Workflow Status (dev)](https://img.shields.io/github/workflow/status/anthonyaxenov/atol-online/CI/dev?logo=github)](https://github.com/anthonyaxenov/atol-online/actions/workflows/ci.yml) | [![codecov dev](https://codecov.io/gh/anthonyaxenov/atol-online/branch/dev/graph/badge.svg?token=WR2IV7FTF0)](https://codecov.io/gh/anthonyaxenov/atol-online) |
+
+## Плюшечки
+
+* Мониторинг ККТ и ФН
+* Фискализация докумнетов на облачной ККТ
+* Валидация данных до отправки документа на ККТ (насколько это возможно, согласно схеме)
+* Расчёты денег в копейках
+* PSR-4 автозагрузка, покрытие настоящими тестами, fluent-setters
+
## Системные требования
-* PHP 7.4+
-* composer
-* php-json
+* php8.0+
+* [composer](https://getcomposer.org/)
+* расширения php (скорее всего, устанавливать их отдельно не придётся):
+ * `php-json`
+ * `php-curl`
+ * `php-mbstring`
+ * `php-tokenizer`
## Начало работы
### Подключение библиотеки
-1. Подключить пакет к вашему проекту:
+1. Подключить пакет к проекту:
```bash
composer require axenov/atol-online
```
-2. В нужном месте проекта объявить параметры, используя константы, и подключить **composer**, если это не сделано ранее:
+2. В нужном месте проекта подключить автозагрузчик composer-зависимостей, если это не сделано ранее:
```php
- require($project_root.'/vendor/autoload.php');
+ require($project_root . '/vendor/autoload.php');
```
где `$project_root` — абсолютный путь к корневой директории вашего проекта.
+ > При использовании фреймворков это обычно не требуется.
### Тестирование кода библиотеки
Файлы тестов находятся в директории `/tests` корня репозитория.
-Для запуска тестов необходимо перейти в корень вашего проекта и выполнить команду:
+Для запуска тестов необходимо перейти в корень репозитория и выполнить одну из команд:
```bash
-./vendor/bin/phpunit
+composer test # обычное тестирование
+composer coverage # тестирование с покрытием
```
-## Настройка ККТ
-
-Для работы с облачной ККТ необходимы следующие параметры:
-* логин;
-* пароль;
-* код группы.
-
-Чтоб получить их, нужно:
-1. авторизоваться на [online.atol.ru](https://online.atol.ru/lk/Account/Login);
-2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**.
- Скачается XML-файл с нужными настройками.
-
-Также для работы потребуются:
-* ИНН продавца;
-* URL места расчёта (ссылка на ваш интернет-сервис).
+После тестирования с покрытием создаётся отчёт в директории `.coverage` в корне репозитория.
## Использование библиотеки
-### Доступные методы и классы
-
Весь исходный код находится в директории [`/src`](/src).
-**Комментарии phpDoc есть буквально к каждому классу, методу или полю.
-Прокомментировано вообще всё.**
+**Комментарии phpdoc есть буквально везде. Прокомментировано вообще всё.**
-1. Обращайтесь к [документации](/docs).
+1. Обращайтесь к [документации библиотеки](/docs).
2. Обращайтесь к [исходному коду](/src).
-3. Обращайтесь к [тестам](/test).
-4. **Используйте подсказки вашей IDE.**
+3. Обращайтесь к [тестам](/tests).
+4. Используйте подсказки вашей IDE.
Тогда у вас не возникнет затруднений.
-Для тех, кто решил подробно разобраться в работе библиотеки, отдельно отмечу нюансы, которые могут ускользнуть от внимания:
-1. Класс `AtolOnline\Api\Kkt` унаследован от `GuzzleHttp\Client` со всеми вытекающими;
-2. Все классы, унаследованные от `AtolOnline\Entities\AtolEntity` приводятся к JSON-строке.
-
-### Общий алгоритм
-
-1. Задать настройки ККТ
-2. Собрать данные о покупателе
-3. Собрать данные о продавце
-4. Собрать данные о предметах расчёта (товары, услуги и пр.)
-5. Создать документ, добавив в него покупателя, продавца и предметы расчёта
-6. Отправить документ на регистрацию:
- 6.1. *Необязательно:* задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа.
-7. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации.
- > Если с документом был передан `callback_url`, то ответ придёт на этот самый URL.
- Если с документом **НЕ** был передан `callback_url` **либо** callback от АТОЛа не пришёл в течение 300 секунд (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа.
-8. Проверить состояние документа (нет необходимости, если передавался `callback_url`):
- 8.1. взять `uuid` ответа, полученного на запрос фискализации;
- 8.2. отправить его в запросе состояния документа.
- > Данные о документе можно получить только в течение 32 суток после успешной фискализации.
-
-## Об отправке электронного чека покупателю
-
-После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в связке с вашей ККТ:
-* **по email**, если в документе указан email клиента;
-* **по смс**:
- * если в документе указан номер телефона клиента;
- * если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД).
-
-> Если заданы email и телефон, то отдаётся приоритет email.
-
## Дополнительные ресурсы
-Функционал, находящийся в разработке: [ROADMAP.md](ROADMAP.md)
-
-Официальные ресурсы АТОЛ Онлайн:
-* **[Вся документация](https://online.atol.ru/lib/)**
-* Telegram-канал: [@atolonline](https://t.me/atolonline)
+* [Документация АТОЛ Онлайн](https://online.atol.ru/lib/)
## Лицензия
-Вы имеете право использовать код из этого репозитория только на условиях **[лицензии MIT](LICENSE)**.
+Вы имеете право использовать и распространят код из этого репозитория на условиях **[лицензии MIT](LICENSE)**.
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index e63c986..0000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Roadmap
-
-Здесь перечислены реализованные функции и находящиеся в разработке.
-
-Порядок их упоминания здесь может не совпадать с порядком реализации.
-
-Эталонная реализация подразумевает полную поддержку всех методов API и обеих схем документов:
-* [Документы прихода, возврата прихода, расхода, возврата расхода](https://online.atol.ru/possystem/v4/schema/sell)
-* [Документы коррекции прихода, коррекции расхода](https://online.atol.ru/possystem/v4/schema/correction)
-
-## Общий функционал библиотеки
-
-- [x] Переключение настроек доступа к ККТ при переключении тестового режима
-- [x] Тесты для класса налоговой ставки (+ массив)
-- [ ] Тесты для класса оплаты (+ массив)
-- [x] Тесты для класса предмета расчёта (+ массив)
-- [x] Тесты для класса клиента
-- [x] Тесты для класса компании
-- [ ] Тесты для класса данных коррекций
-- [ ] Тесты для класса документа
-- [ ] Тесты для класса ответа ККТ
-- [ ] Тесты для регистрации документа прихода
-- [ ] Тесты для регистрации документа возврата прихода
-- [ ] Тесты для регистрации документа коррекции прихода
-- [ ] Тесты для регистрации документа расхода
-- [ ] Тесты для регистрации документа возврата расхода
-- [ ] Тесты для регистрации документа коррекции расхода
-- [ ] Вообще все расчёты вообще везде должны быть строго в копейках.
- Рубли (дроби) должны быть только в JSON-представлениях
-
-## Поддержка методов API (регистрация документов)
-
-- [x] приход
-- [x] расход
-- [x] возврат прихода
-- [x] возврат расхода
-- [x] коррекция прихода
-- [x] коррекция расхода
-- [x] проверка статуса документа
-
-## Документы прихода, возврата прихода, расхода, возврата расхода
-
-- [x] Пoддержка `receipt.client` (обязательный)
-- [x] Пoддержка `receipt.company` (обязательный)
-- [x] Пoддержка `receipt.items` (обязательный)
-- [x] Пoддержка `receipt.total` (обязательный)
-- [x] Пoддержка `receipt.payments` (обязательный)
-- [x] Пoддержка `receipt.vats`
-- [ ] Пoддержка `receipt.additional_check_props`
-- [x] Пoддержка `receipt.cashier`
-- [ ] Пoддержка `receipt.additional_user_props`
-- [ ] Пoддержка `receipt.agent_info`
-- [ ] Пoддержка `receipt.supplier_info`
-- [ ] Пoддержка `receipt.items.agent_info`
-- [ ] Пoддержка `receipt.items.supplier_info`
-- [ ] Пoддержка `receipt.items.nomenclature_code`
-- [ ] Пoддержка `receipt.items.excise`
-- [ ] Пoддержка `receipt.items.country_code`
-- [ ] Пoддержка `receipt.items.declaration_number`
-
-## Документы коррекции прихода, коррекции расхода
-
-- [x] Пoддержка `correction.company` (обязательный)
-- [x] Пoддержка `correction.vats` (обязательный)
-- [x] Пoддержка `correction.correction_info` (обязательный)
-- [x] Пoддержка `correction.cashier`
diff --git a/composer.json b/composer.json
index 352104b..96ff66e 100644
--- a/composer.json
+++ b/composer.json
@@ -1,22 +1,26 @@
{
"name": "axenov/atol-online",
- "description": "Library to use cloud cash register in e-commerce according to Russian Federal Law #54",
+ "description": "Библиотека для работы с API АТОЛ Онлайн (облачные ККТ для приёма платежей по 54-ФЗ)",
"license": "MIT",
"type": "library",
"keywords": [
+ "api",
+ "e-commerce",
"54-fz",
"54-фз",
"kkt",
"ккт",
- "e-commerce",
"cash",
"cash register",
"payment",
"payment system",
"atol",
"атол",
- "atol-online",
- "атол онлайн"
+ "atol online",
+ "атол онлайн",
+ "fiscalization",
+ "фискализация",
+ "чек"
],
"homepage": "https://github.com/anthonyaxenov/atol-online",
"readme": "https://github.com/anthonyaxenov/atol-online/blob/master/README.md",
@@ -28,37 +32,52 @@
}
],
"support": {
+ "rss": "https://github.com/anthonyaxenov/atol-online/discussions/categories/announcements",
+ "chat": "https://github.com/anthonyaxenov/atol-online/discussions",
"source": "https://github.com/anthonyaxenov/atol-online",
"issues": "https://github.com/anthonyaxenov/atol-online/issues",
"docs": "https://github.com/anthonyaxenov/atol-online/blob/master/docs/readme.md"
},
"funding": [
+ {
+ "type": "Patreon",
+ "url": "https://www.patreon.com/anthonyaxenov"
+ },
{
"type": "Yoomoney",
"url": "https://yoomoney.ru/to/41001685237530"
}
],
"require": {
- "php": ">=7.4",
+ "php": ">=8.0",
"ext-json": "*",
- "guzzlehttp/guzzle": "^6.5",
- "psr/log": "^1.1",
- "ramsey/uuid": "^3.9"
+ "ext-mbstring": "*",
+ "guzzlehttp/guzzle": "^7.4",
+ "psr/log": "^3",
+ "ramsey/uuid": "^4.2",
+ "myclabs/php-enum": "^1.8",
+ "illuminate/collections": "^8.70",
+ "jetbrains/phpstorm-attributes": "^1.0"
},
"require-dev": {
- "phpunit/phpunit": "^8.5"
+ "phpunit/phpunit": "^9.5"
},
"autoload": {
+ "psr-4": {
+ "AtolOnline\\": "src/"
+ }
+ },
+ "autoload-dev": {
"classmap": [
- "src/AtolOnline/Api/",
- "src/AtolOnline/Exceptions/",
- "src/AtolOnline/Entities/",
- "src/AtolOnline/Traits/",
- "src/AtolOnline/Constants/",
"tests/"
- ],
- "files": [
- "src/helpers.php"
]
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit --colors=always",
+ "coverage": "php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html .coverage"
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "sort-packages": true
}
}
diff --git a/composer.lock b/composer.lock
index 47340ed..4d9eef4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,41 +4,109 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "5e1b55b0c481ba8d05be29bf5310f5ef",
+ "content-hash": "4877f27cd59b6558eea01644e2d520ef",
"packages": [
{
- "name": "guzzlehttp/guzzle",
- "version": "6.5.5",
+ "name": "brick/math",
+ "version": "0.9.3",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/guzzle.git",
- "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
+ "url": "https://github.com/brick/math.git",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
- "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
+ "url": "https://api.github.com/repos/brick/math/zipball/ca57d18f028f84f777b2168cd1911b0dee2343ae",
+ "reference": "ca57d18f028f84f777b2168cd1911b0dee2343ae",
"shasum": ""
},
"require": {
"ext-json": "*",
- "guzzlehttp/promises": "^1.0",
- "guzzlehttp/psr7": "^1.6.1",
- "php": ">=5.5",
- "symfony/polyfill-intl-idn": "^1.17.0"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.0",
+ "vimeo/psalm": "4.9.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "brick",
+ "math"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/brick/math",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-08-15T20:50:18+00:00"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "7.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
+ "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.5",
+ "guzzlehttp/psr7": "^1.8.3 || ^2.1",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
"ext-curl": "*",
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
- "psr/log": "^1.1"
+ "php-http/client-integration-tests": "^3.0",
+ "phpunit/phpunit": "^8.5.5 || ^9.3.5",
+ "psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "6.5-dev"
+ "dev-master": "7.4-dev"
}
},
"autoload": {
@@ -54,41 +122,86 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle is a PHP HTTP client library",
- "homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
+ "psr-18",
+ "psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
- "source": "https://github.com/guzzle/guzzle/tree/6.5"
+ "source": "https://github.com/guzzle/guzzle/tree/7.4.1"
},
- "time": "2020-06-16T21:01:06+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-12-06T18:43:05+00:00"
},
{
"name": "guzzlehttp/promises",
- "version": "1.4.1",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
- "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
- "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
+ "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da",
"shasum": ""
},
"require": {
@@ -100,7 +213,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "1.5-dev"
}
},
"autoload": {
@@ -116,10 +229,25 @@
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
@@ -128,35 +256,52 @@
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
- "source": "https://github.com/guzzle/promises/tree/1.4.1"
+ "source": "https://github.com/guzzle/promises/tree/1.5.1"
},
- "time": "2021-03-07T09:25:29+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-22T20:56:57+00:00"
},
{
"name": "guzzlehttp/psr7",
- "version": "1.8.2",
+ "version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
- "reference": "dc960a912984efb74d0a90222870c72c87f10c91"
+ "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
- "reference": "dc960a912984efb74d0a90222870c72c87f10c91",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
+ "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72",
"shasum": ""
},
"require": {
- "php": ">=5.4.0",
- "psr/http-message": "~1.0",
- "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.0",
+ "ralouphie/getallheaders": "^3.0"
},
"provide": {
+ "psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
- "ext-zlib": "*",
- "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "http-interop/http-factory-tests": "^0.9",
+ "phpunit/phpunit": "^8.5.8 || ^9.3.10"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
@@ -164,30 +309,53 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.7-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
- },
- "files": [
- "src/functions_include.php"
- ]
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
{
"name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
@@ -203,59 +371,428 @@
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
- "source": "https://github.com/guzzle/psr7/tree/1.8.2"
+ "source": "https://github.com/guzzle/psr7/tree/2.1.0"
},
- "time": "2021-04-26T09:17:50+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-06T17:43:30+00:00"
},
{
- "name": "paragonie/random_compat",
- "version": "v9.99.99",
+ "name": "illuminate/collections",
+ "version": "v8.75.0",
"source": {
"type": "git",
- "url": "https://github.com/paragonie/random_compat.git",
- "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
+ "url": "https://github.com/illuminate/collections.git",
+ "reference": "5a018387352afa2af30fd2be0a78c31e93295720"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
- "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
+ "url": "https://api.github.com/repos/illuminate/collections/zipball/5a018387352afa2af30fd2be0a78c31e93295720",
+ "reference": "5a018387352afa2af30fd2be0a78c31e93295720",
"shasum": ""
},
"require": {
- "php": "^7"
- },
- "require-dev": {
- "phpunit/phpunit": "4.*|5.*",
- "vimeo/psalm": "^1"
+ "illuminate/contracts": "^8.0",
+ "illuminate/macroable": "^8.0",
+ "php": "^7.3|^8.0"
},
"suggest": {
- "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ "symfony/var-dumper": "Required to use the dump method (^5.4)."
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ },
+ "files": [
+ "helpers.php"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
- "name": "Paragon Initiative Enterprises",
- "email": "security@paragonie.com",
- "homepage": "https://paragonie.com"
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
}
],
- "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "description": "The Illuminate Collections package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2021-12-07T14:48:29+00:00"
+ },
+ {
+ "name": "illuminate/contracts",
+ "version": "v8.75.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/contracts.git",
+ "reference": "b07755f7c456cf587dfbfd6f0854f9f7c1a34b2f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/contracts/zipball/b07755f7c456cf587dfbfd6f0854f9f7c1a34b2f",
+ "reference": "b07755f7c456cf587dfbfd6f0854f9f7c1a34b2f",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0",
+ "psr/container": "^1.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Contracts\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Contracts package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2021-12-07T08:18:44+00:00"
+ },
+ {
+ "name": "illuminate/macroable",
+ "version": "v8.75.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/illuminate/macroable.git",
+ "reference": "aed81891a6e046fdee72edd497f822190f61c162"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/illuminate/macroable/zipball/aed81891a6e046fdee72edd497f822190f61c162",
+ "reference": "aed81891a6e046fdee72edd497f822190f61c162",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "8.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Illuminate\\Support\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Illuminate Macroable package.",
+ "homepage": "https://laravel.com",
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2021-11-16T13:57:03+00:00"
+ },
+ {
+ "name": "jetbrains/phpstorm-attributes",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/JetBrains/phpstorm-attributes.git",
+ "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/JetBrains/phpstorm-attributes/zipball/a7a83ae5df4dd3c0875484483de19de8edf60a9f",
+ "reference": "a7a83ae5df4dd3c0875484483de19de8edf60a9f",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "JetBrains\\PhpStorm\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "JetBrains",
+ "homepage": "https://www.jetbrains.com"
+ }
+ ],
+ "description": "PhpStorm specific attributes",
"keywords": [
- "csprng",
- "polyfill",
- "pseudorandom",
- "random"
+ "attributes",
+ "jetbrains",
+ "phpstorm"
],
"support": {
- "email": "info@paragonie.com",
- "issues": "https://github.com/paragonie/random_compat/issues",
- "source": "https://github.com/paragonie/random_compat"
+ "issues": "https://youtrack.jetbrains.com/newIssue?project=WI",
+ "source": "https://github.com/JetBrains/phpstorm-attributes/tree/1.0"
},
- "time": "2018-07-02T15:55:56+00:00"
+ "time": "2020-11-17T11:09:47+00:00"
+ },
+ {
+ "name": "myclabs/php-enum",
+ "version": "1.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/php-enum.git",
+ "reference": "b942d263c641ddb5190929ff840c68f78713e937"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937",
+ "reference": "b942d263c641ddb5190929ff840c68f78713e937",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": "^7.3 || ^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "1.*",
+ "vimeo/psalm": "^4.6.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MyCLabs\\Enum\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP Enum contributors",
+ "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+ }
+ ],
+ "description": "PHP Enum implementation",
+ "homepage": "http://github.com/myclabs/php-enum",
+ "keywords": [
+ "enum"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/php-enum/issues",
+ "source": "https://github.com/myclabs/php-enum/tree/1.8.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/mnapoli",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-07-05T08:18:36+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/1.1.2"
+ },
+ "time": "2021-11-05T16:50:12+00:00"
+ },
+ {
+ "name": "psr/http-client",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-client/tree/master"
+ },
+ "time": "2020-06-29T06:28:15+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0.0",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory/tree/master"
+ },
+ "time": "2019-04-30T12:38:16+00:00"
},
{
"name": "psr/http-message",
@@ -312,30 +849,30 @@
},
{
"name": "psr/log",
- "version": "1.1.4",
+ "version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
- "reference": "d49695b909c3b7628b6289db5479a1c204601f11"
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11",
- "reference": "d49695b909c3b7628b6289db5479a1c204601f11",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
- "Psr\\Log\\": "Psr/Log/"
+ "Psr\\Log\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -356,9 +893,60 @@
"psr-3"
],
"support": {
- "source": "https://github.com/php-fig/log/tree/1.1.4"
+ "source": "https://github.com/php-fig/log/tree/3.0.0"
},
- "time": "2021-05-03T11:20:27+00:00"
+ "time": "2021-07-14T16:46:02+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/simple-cache/tree/master"
+ },
+ "time": "2017-10-23T01:57:42+00:00"
},
{
"name": "ralouphie/getallheaders",
@@ -405,54 +993,146 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
- "name": "ramsey/uuid",
- "version": "3.9.3",
+ "name": "ramsey/collection",
+ "version": "1.2.2",
"source": {
"type": "git",
- "url": "https://github.com/ramsey/uuid.git",
- "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92"
+ "url": "https://github.com/ramsey/collection.git",
+ "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/7e1633a6964b48589b142d60542f9ed31bd37a92",
- "reference": "7e1633a6964b48589b142d60542f9ed31bd37a92",
+ "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a",
+ "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a",
"shasum": ""
},
"require": {
+ "php": "^7.3 || ^8",
+ "symfony/polyfill-php81": "^1.23"
+ },
+ "require-dev": {
+ "captainhook/captainhook": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "ergebnis/composer-normalize": "^2.6",
+ "fakerphp/faker": "^1.5",
+ "hamcrest/hamcrest-php": "^2",
+ "jangregor/phpstan-prophecy": "^0.8",
+ "mockery/mockery": "^1.3",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpstan/extension-installer": "^1",
+ "phpstan/phpstan": "^0.12.32",
+ "phpstan/phpstan-mockery": "^0.12.5",
+ "phpstan/phpstan-phpunit": "^0.12.11",
+ "phpunit/phpunit": "^8.5 || ^9",
+ "psy/psysh": "^0.10.4",
+ "slevomat/coding-standard": "^6.3",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^4.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Collection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "A PHP library for representing and manipulating collections.",
+ "keywords": [
+ "array",
+ "collection",
+ "hash",
+ "map",
+ "queue",
+ "set"
+ ],
+ "support": {
+ "issues": "https://github.com/ramsey/collection/issues",
+ "source": "https://github.com/ramsey/collection/tree/1.2.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ramsey",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-10T03:01:02+00:00"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "4.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
+ "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.8 || ^0.9",
"ext-json": "*",
- "paragonie/random_compat": "^1 | ^2 | 9.99.99",
- "php": "^5.4 | ^7 | ^8",
- "symfony/polyfill-ctype": "^1.8"
+ "php": "^7.2 || ^8.0",
+ "ramsey/collection": "^1.0",
+ "symfony/polyfill-ctype": "^1.8",
+ "symfony/polyfill-php80": "^1.14"
},
"replace": {
"rhumsaa/uuid": "self.version"
},
"require-dev": {
- "codeception/aspect-mock": "^1 | ^2",
- "doctrine/annotations": "^1.2",
- "goaop/framework": "1.0.0-alpha.2 | ^1 | ^2.1",
- "jakub-onderka/php-parallel-lint": "^1",
- "mockery/mockery": "^0.9.11 | ^1",
+ "captainhook/captainhook": "^5.10",
+ "captainhook/plugin-composer": "^5.3",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "doctrine/annotations": "^1.8",
+ "ergebnis/composer-normalize": "^2.15",
+ "mockery/mockery": "^1.3",
"moontoast/math": "^1.1",
"paragonie/random-lib": "^2",
- "php-mock/php-mock-phpunit": "^0.3 | ^1.1",
- "phpunit/phpunit": "^4.8 | ^5.4 | ^6.5",
- "squizlabs/php_codesniffer": "^3.5"
+ "php-mock/php-mock": "^2.2",
+ "php-mock/php-mock-mockery": "^1.3",
+ "php-parallel-lint/php-parallel-lint": "^1.1",
+ "phpbench/phpbench": "^1.0",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-mockery": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^8.5 || ^9",
+ "slevomat/coding-standard": "^7.0",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^4.9"
},
"suggest": {
- "ext-ctype": "Provides support for PHP Ctype functions",
- "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
- "ext-openssl": "Provides the OpenSSL extension for use with the OpenSslGenerator",
- "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
- "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
+ "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
+ "ext-ctype": "Enables faster processing of character classification using ctype functions.",
+ "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
+ "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
- "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.x-dev"
+ "dev-main": "4.x-dev"
+ },
+ "captainhook": {
+ "force-install": true
}
},
"autoload": {
@@ -467,23 +1147,7 @@
"license": [
"MIT"
],
- "authors": [
- {
- "name": "Ben Ramsey",
- "email": "ben@benramsey.com",
- "homepage": "https://benramsey.com"
- },
- {
- "name": "Marijn Huizendveld",
- "email": "marijn.huizendveld@gmail.com"
- },
- {
- "name": "Thibaud Fabre",
- "email": "thibaud@aztech.io"
- }
- ],
- "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
- "homepage": "https://github.com/ramsey/uuid",
+ "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"keywords": [
"guid",
"identifier",
@@ -491,24 +1155,99 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
- "rss": "https://github.com/ramsey/uuid/releases.atom",
- "source": "https://github.com/ramsey/uuid",
- "wiki": "https://github.com/ramsey/uuid/wiki"
+ "source": "https://github.com/ramsey/uuid/tree/4.2.3"
},
- "time": "2020-02-21T04:36:14+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/ramsey",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-09-25T23:10:38+00:00"
},
{
- "name": "symfony/polyfill-ctype",
- "version": "v1.22.1",
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.0.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
- "reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
+ "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-11-01T23:48:49+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"shasum": ""
},
"require": {
@@ -520,7 +1259,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -558,7 +1297,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.23.0"
},
"funding": [
{
@@ -574,34 +1313,29 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2021-02-19T12:13:01+00:00"
},
{
- "name": "symfony/polyfill-intl-idn",
- "version": "v1.22.1",
+ "name": "symfony/polyfill-php80",
+ "version": "v1.23.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-intl-idn.git",
- "reference": "2d63434d922daf7da8dd863e7907e67ee3031483"
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/2d63434d922daf7da8dd863e7907e67ee3031483",
- "reference": "2d63434d922daf7da8dd863e7907e67ee3031483",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be",
+ "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be",
"shasum": ""
},
"require": {
- "php": ">=7.1",
- "symfony/polyfill-intl-normalizer": "^1.10",
- "symfony/polyfill-php72": "^1.10"
- },
- "suggest": {
- "ext-intl": "For best performance"
+ "php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -610,10 +1344,13 @@
},
"autoload": {
"psr-4": {
- "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ "Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -622,30 +1359,28 @@
],
"authors": [
{
- "name": "Laurent Bassin",
- "email": "laurent@bassin.info"
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
},
{
- "name": "Trevor Rowbotham",
- "email": "trevor.rowbotham@pm.me"
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
- "idn",
- "intl",
"polyfill",
"portable",
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1"
},
"funding": [
{
@@ -661,32 +1396,29 @@
"type": "tidelift"
}
],
- "time": "2021-01-22T09:19:47+00:00"
+ "time": "2021-07-28T13:41:28+00:00"
},
{
- "name": "symfony/polyfill-intl-normalizer",
- "version": "v1.22.1",
+ "name": "symfony/polyfill-php81",
+ "version": "v1.23.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248"
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248",
- "reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436",
+ "reference": "e66119f3de95efc359483f810c4c3e6436279436",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
- "suggest": {
- "ext-intl": "For best performance"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "1.22-dev"
+ "dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -695,7 +1427,7 @@
},
"autoload": {
"psr-4": {
- "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ "Symfony\\Polyfill\\Php81\\": ""
},
"files": [
"bootstrap.php"
@@ -718,18 +1450,16 @@
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
- "intl",
- "normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1"
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0"
},
"funding": [
{
@@ -745,83 +1475,7 @@
"type": "tidelift"
}
],
- "time": "2021-01-22T09:19:47+00:00"
- },
- {
- "name": "symfony/polyfill-php72",
- "version": "v1.22.1",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php72.git",
- "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9",
- "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "1.22-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Php72\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.1"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2021-05-21T13:25:03+00:00"
}
],
"packages-dev": [
@@ -953,17 +1607,73 @@
"time": "2020-11-13T09:40:50+00:00"
},
{
- "name": "phar-io/manifest",
- "version": "2.0.1",
+ "name": "nikic/php-parser",
+ "version": "v4.13.2",
"source": {
"type": "git",
- "url": "https://github.com/phar-io/manifest.git",
- "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133"
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "210577fe3cf7badcc5814d99455df46564f3c077"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
- "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
+ "reference": "210577fe3cf7badcc5814d99455df46564f3c077",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
+ },
+ "time": "2021-11-30T19:35:32+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
"shasum": ""
},
"require": {
@@ -1008,9 +1718,9 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
- "source": "https://github.com/phar-io/manifest/tree/master"
+ "source": "https://github.com/phar-io/manifest/tree/2.0.3"
},
- "time": "2020-06-27T14:33:11+00:00"
+ "time": "2021-07-20T11:28:43+00:00"
},
{
"name": "phar-io/version",
@@ -1118,16 +1828,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
- "version": "5.2.2",
+ "version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
- "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
"shasum": ""
},
"require": {
@@ -1138,7 +1848,8 @@
"webmozart/assert": "^1.9.1"
},
"require-dev": {
- "mockery/mockery": "~1.3.2"
+ "mockery/mockery": "~1.3.2",
+ "psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@@ -1168,22 +1879,22 @@
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
- "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
},
- "time": "2020-09-03T19:13:55+00:00"
+ "time": "2021-10-19T17:43:47+00:00"
},
{
"name": "phpdocumentor/type-resolver",
- "version": "1.4.0",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
- "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
+ "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
"shasum": ""
},
"require": {
@@ -1191,7 +1902,8 @@
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
- "ext-tokenizer": "*"
+ "ext-tokenizer": "*",
+ "psalm/phar": "^4.8"
},
"type": "library",
"extra": {
@@ -1217,39 +1929,39 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
},
- "time": "2020-09-17T18:55:26+00:00"
+ "time": "2021-10-02T14:08:47+00:00"
},
{
"name": "phpspec/prophecy",
- "version": "1.13.0",
+ "version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
+ "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
- "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
+ "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.2",
- "php": "^7.2 || ~8.0, <8.1",
+ "php": "^7.2 || ~8.0, <8.2",
"phpdocumentor/reflection-docblock": "^5.2",
"sebastian/comparator": "^3.0 || ^4.0",
"sebastian/recursion-context": "^3.0 || ^4.0"
},
"require-dev": {
- "phpspec/phpspec": "^6.0",
+ "phpspec/phpspec": "^6.0 || ^7.0",
"phpunit/phpunit": "^8.0 || ^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.11.x-dev"
+ "dev-master": "1.x-dev"
}
},
"autoload": {
@@ -1284,46 +1996,50 @@
],
"support": {
"issues": "https://github.com/phpspec/prophecy/issues",
- "source": "https://github.com/phpspec/prophecy/tree/1.13.0"
+ "source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
},
- "time": "2021-03-17T13:42:18+00:00"
+ "time": "2021-12-08T12:19:24+00:00"
},
{
"name": "phpunit/php-code-coverage",
- "version": "7.0.14",
+ "version": "9.2.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c"
+ "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/bb7c9a210c72e4709cdde67f8b7362f672f2225c",
- "reference": "bb7c9a210c72e4709cdde67f8b7362f672f2225c",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687",
+ "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687",
"shasum": ""
},
"require": {
"ext-dom": "*",
+ "ext-libxml": "*",
"ext-xmlwriter": "*",
- "php": ">=7.2",
- "phpunit/php-file-iterator": "^2.0.2",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-token-stream": "^3.1.1 || ^4.0",
- "sebastian/code-unit-reverse-lookup": "^1.0.1",
- "sebastian/environment": "^4.2.2",
- "sebastian/version": "^2.0.1",
- "theseer/tokenizer": "^1.1.3"
+ "nikic/php-parser": "^4.13.0",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.3",
+ "phpunit/php-text-template": "^2.0.2",
+ "sebastian/code-unit-reverse-lookup": "^2.0.2",
+ "sebastian/complexity": "^2.0",
+ "sebastian/environment": "^5.1.2",
+ "sebastian/lines-of-code": "^1.0.3",
+ "sebastian/version": "^3.0.1",
+ "theseer/tokenizer": "^1.2.0"
},
"require-dev": {
- "phpunit/phpunit": "^8.2.2"
+ "phpunit/phpunit": "^9.3"
},
"suggest": {
- "ext-xdebug": "^2.7.2"
+ "ext-pcov": "*",
+ "ext-xdebug": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "7.0-dev"
+ "dev-master": "9.2-dev"
}
},
"autoload": {
@@ -1351,7 +2067,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.14"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10"
},
"funding": [
{
@@ -1359,32 +2075,32 @@
"type": "github"
}
],
- "time": "2020-12-02T13:39:03+00:00"
+ "time": "2021-12-05T09:12:13+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "2.0.3",
+ "version": "3.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357"
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4b49fb70f067272b659ef0174ff9ca40fdaa6357",
- "reference": "4b49fb70f067272b659ef0174ff9ca40fdaa6357",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^8.5"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -1411,7 +2127,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
- "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.3"
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
},
"funding": [
{
@@ -1419,26 +2135,97 @@
"type": "github"
}
],
- "time": "2020-11-30T08:25:21+00:00"
+ "time": "2021-12-02T12:48:52+00:00"
},
{
- "name": "phpunit/php-text-template",
- "version": "1.2.1",
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
"autoload": {
"classmap": [
"src/"
@@ -1462,34 +2249,40 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
- "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
},
- "time": "2015-06-21T13:50:34+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
},
{
"name": "phpunit/php-timer",
- "version": "2.1.3",
+ "version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662"
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662",
- "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^8.5"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.1-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
@@ -1515,7 +2308,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
- "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3"
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
},
"funding": [
{
@@ -1523,80 +2316,20 @@
"type": "github"
}
],
- "time": "2020-11-30T08:20:02+00:00"
- },
- {
- "name": "phpunit/php-token-stream",
- "version": "4.0.4",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3",
- "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3",
- "shasum": ""
- },
- "require": {
- "ext-tokenizer": "*",
- "php": "^7.3 || ^8.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Wrapper around PHP's tokenizer extension.",
- "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
- "keywords": [
- "tokenizer"
- ],
- "support": {
- "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
- "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
- },
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "abandoned": true,
- "time": "2020-08-04T08:28:15+00:00"
+ "time": "2020-10-26T13:16:10+00:00"
},
{
"name": "phpunit/phpunit",
- "version": "8.5.15",
+ "version": "9.5.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "038d4196d8e8cb405cd5e82cedfe413ad6eef9ef"
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/038d4196d8e8cb405cd5e82cedfe413ad6eef9ef",
- "reference": "038d4196d8e8cb405cd5e82cedfe413ad6eef9ef",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
+ "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
"shasum": ""
},
"require": {
@@ -1607,32 +2340,35 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.10.0",
- "phar-io/manifest": "^2.0.1",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
"phar-io/version": "^3.0.2",
- "php": ">=7.2",
- "phpspec/prophecy": "^1.10.3",
- "phpunit/php-code-coverage": "^7.0.12",
- "phpunit/php-file-iterator": "^2.0.2",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-timer": "^2.1.2",
- "sebastian/comparator": "^3.0.2",
- "sebastian/diff": "^3.0.2",
- "sebastian/environment": "^4.2.3",
- "sebastian/exporter": "^3.1.2",
- "sebastian/global-state": "^3.0.0",
- "sebastian/object-enumerator": "^3.0.3",
- "sebastian/resource-operations": "^2.0.1",
- "sebastian/type": "^1.1.3",
- "sebastian/version": "^2.0.1"
+ "php": ">=7.3",
+ "phpspec/prophecy": "^1.12.1",
+ "phpunit/php-code-coverage": "^9.2.7",
+ "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.3",
+ "phpunit/php-timer": "^5.0.2",
+ "sebastian/cli-parser": "^1.0.1",
+ "sebastian/code-unit": "^1.0.6",
+ "sebastian/comparator": "^4.0.5",
+ "sebastian/diff": "^4.0.3",
+ "sebastian/environment": "^5.1.3",
+ "sebastian/exporter": "^4.0.3",
+ "sebastian/global-state": "^5.0.1",
+ "sebastian/object-enumerator": "^4.0.3",
+ "sebastian/resource-operations": "^3.0.3",
+ "sebastian/type": "^2.3.4",
+ "sebastian/version": "^3.0.2"
},
"require-dev": {
- "ext-pdo": "*"
+ "ext-pdo": "*",
+ "phpspec/prophecy-phpunit": "^2.0.1"
},
"suggest": {
"ext-soap": "*",
- "ext-xdebug": "*",
- "phpunit/php-invoker": "^2.0.0"
+ "ext-xdebug": "*"
},
"bin": [
"phpunit"
@@ -1640,12 +2376,15 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "8.5-dev"
+ "dev-master": "9.5-dev"
}
},
"autoload": {
"classmap": [
"src/"
+ ],
+ "files": [
+ "src/Framework/Assert/Functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@@ -1668,7 +2407,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.15"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10"
},
"funding": [
{
@@ -1680,32 +2419,144 @@
"type": "github"
}
],
- "time": "2021-03-17T07:27:54+00:00"
+ "time": "2021-09-25T07:38:51+00:00"
},
{
- "name": "sebastian/code-unit-reverse-lookup",
- "version": "1.0.2",
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619"
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619",
- "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"shasum": ""
},
"require": {
- "php": ">=5.6"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^8.5"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:08:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -1727,7 +2578,7 @@
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
- "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2"
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
},
"funding": [
{
@@ -1735,34 +2586,34 @@
"type": "github"
}
],
- "time": "2020-11-30T08:15:22+00:00"
+ "time": "2020-09-28T05:30:19+00:00"
},
{
"name": "sebastian/comparator",
- "version": "3.0.3",
+ "version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "1071dfcef776a57013124ff35e1fc41ccd294758"
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758",
- "reference": "1071dfcef776a57013124ff35e1fc41ccd294758",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
"shasum": ""
},
"require": {
- "php": ">=7.1",
- "sebastian/diff": "^3.0",
- "sebastian/exporter": "^3.1"
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
},
"require-dev": {
- "phpunit/phpunit": "^8.5"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -1801,7 +2652,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.3"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
},
"funding": [
{
@@ -1809,33 +2660,90 @@
"type": "github"
}
],
- "time": "2020-11-30T08:04:30+00:00"
+ "time": "2020-10-26T15:49:45+00:00"
},
{
- "name": "sebastian/diff",
- "version": "3.0.3",
+ "name": "sebastian/complexity",
+ "version": "2.0.2",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211"
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
- "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "nikic/php-parser": "^4.7",
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^7.5 || ^8.0",
- "symfony/process": "^2 || ^3.3 || ^4"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:52:27+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -1867,7 +2775,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
- "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3"
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
},
"funding": [
{
@@ -1875,27 +2783,27 @@
"type": "github"
}
],
- "time": "2020-11-30T07:59:04+00:00"
+ "time": "2020-10-26T13:10:38+00:00"
},
{
"name": "sebastian/environment",
- "version": "4.2.4",
+ "version": "5.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0"
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
- "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^7.5"
+ "phpunit/phpunit": "^9.3"
},
"suggest": {
"ext-posix": "*"
@@ -1903,7 +2811,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.2-dev"
+ "dev-master": "5.1-dev"
}
},
"autoload": {
@@ -1930,7 +2838,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
- "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4"
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
},
"funding": [
{
@@ -1938,34 +2846,34 @@
"type": "github"
}
],
- "time": "2020-11-30T07:53:42+00:00"
+ "time": "2020-09-28T05:52:38+00:00"
},
{
"name": "sebastian/exporter",
- "version": "3.1.3",
+ "version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e"
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e",
- "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
"shasum": ""
},
"require": {
- "php": ">=7.0",
- "sebastian/recursion-context": "^3.0"
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
},
"require-dev": {
"ext-mbstring": "*",
- "phpunit/phpunit": "^6.0"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.1.x-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -2000,14 +2908,14 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.3"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
},
"funding": [
{
@@ -2015,30 +2923,30 @@
"type": "github"
}
],
- "time": "2020-11-30T07:47:53+00:00"
+ "time": "2021-11-11T14:18:36+00:00"
},
{
"name": "sebastian/global-state",
- "version": "3.0.1",
+ "version": "5.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b"
+ "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b",
- "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49",
+ "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49",
"shasum": ""
},
"require": {
- "php": ">=7.2",
- "sebastian/object-reflector": "^1.1.1",
- "sebastian/recursion-context": "^3.0"
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
},
"require-dev": {
"ext-dom": "*",
- "phpunit/phpunit": "^8.0"
+ "phpunit/phpunit": "^9.3"
},
"suggest": {
"ext-uopz": "*"
@@ -2046,7 +2954,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
@@ -2071,7 +2979,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.1"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3"
},
"funding": [
{
@@ -2079,34 +2987,91 @@
"type": "github"
}
],
- "time": "2020-11-30T07:43:24+00:00"
+ "time": "2021-06-11T13:31:12+00:00"
},
{
- "name": "sebastian/object-enumerator",
- "version": "3.0.4",
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.3",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2"
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
- "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"shasum": ""
},
"require": {
- "php": ">=7.0",
- "sebastian/object-reflector": "^1.1.1",
- "sebastian/recursion-context": "^3.0"
+ "nikic/php-parser": "^4.6",
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^6.0"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0.x-dev"
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-28T06:42:11+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -2128,7 +3093,7 @@
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
- "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4"
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
},
"funding": [
{
@@ -2136,32 +3101,32 @@
"type": "github"
}
],
- "time": "2020-11-30T07:40:27+00:00"
+ "time": "2020-10-26T13:12:34+00:00"
},
{
"name": "sebastian/object-reflector",
- "version": "1.1.2",
+ "version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
- "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d"
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
- "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
"shasum": ""
},
"require": {
- "php": ">=7.0"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^6.0"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -2183,7 +3148,7 @@
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
- "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2"
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
},
"funding": [
{
@@ -2191,32 +3156,32 @@
"type": "github"
}
],
- "time": "2020-11-30T07:37:18+00:00"
+ "time": "2020-10-26T13:14:26+00:00"
},
{
"name": "sebastian/recursion-context",
- "version": "3.0.1",
+ "version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb"
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb",
- "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
"shasum": ""
},
"require": {
- "php": ">=7.0"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^6.0"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0.x-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -2246,7 +3211,7 @@
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1"
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
},
"funding": [
{
@@ -2254,29 +3219,32 @@
"type": "github"
}
],
- "time": "2020-11-30T07:34:24+00:00"
+ "time": "2020-10-26T13:17:30+00:00"
},
{
"name": "sebastian/resource-operations",
- "version": "2.0.2",
+ "version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3"
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3",
- "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"shasum": ""
},
"require": {
- "php": ">=7.1"
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2298,7 +3266,7 @@
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
- "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2"
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
},
"funding": [
{
@@ -2306,32 +3274,32 @@
"type": "github"
}
],
- "time": "2020-11-30T07:30:19+00:00"
+ "time": "2020-09-28T06:45:17+00:00"
},
{
"name": "sebastian/type",
- "version": "1.1.4",
+ "version": "2.3.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
- "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4"
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4",
- "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
"shasum": ""
},
"require": {
- "php": ">=7.2"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^8.2"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
@@ -2354,7 +3322,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
- "source": "https://github.com/sebastianbergmann/type/tree/1.1.4"
+ "source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
},
"funding": [
{
@@ -2362,29 +3330,29 @@
"type": "github"
}
],
- "time": "2020-11-30T07:25:11+00:00"
+ "time": "2021-06-15T12:49:02+00:00"
},
{
"name": "sebastian/version",
- "version": "2.0.1",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
"shasum": ""
},
"require": {
- "php": ">=5.6"
+ "php": ">=7.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -2407,22 +3375,28 @@
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
- "source": "https://github.com/sebastianbergmann/version/tree/master"
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
},
- "time": "2016-10-03T07:35:21+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
},
{
"name": "theseer/tokenizer",
- "version": "1.2.0",
+ "version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
- "reference": "75a63c33a8577608444246075ea0af0d052e452a"
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
- "reference": "75a63c33a8577608444246075ea0af0d052e452a",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"shasum": ""
},
"require": {
@@ -2451,7 +3425,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
- "source": "https://github.com/theseer/tokenizer/tree/master"
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
},
"funding": [
{
@@ -2459,7 +3433,7 @@
"type": "github"
}
],
- "time": "2020-07-12T23:59:07+00:00"
+ "time": "2021-07-28T10:34:58+00:00"
},
{
"name": "webmozart/assert",
@@ -2526,9 +3500,10 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
- "php": ">=7.4",
- "ext-json": "*"
+ "php": ">=8.0",
+ "ext-json": "*",
+ "ext-mbstring": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.0.0"
+ "plugin-api-version": "2.1.0"
}
diff --git a/docs/client.md b/docs/client.md
deleted file mode 100644
index d22484a..0000000
--- a/docs/client.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Работа с клиентами (покупателями)
-
-[Вернуться к содержанию](readme.md)
-
----
-
-Объект покупателя инициализируется следующим образом:
-
-```php
-$customer = new AtolOnline\Entities\Client();
-```
-
-У объекта покупателя могут быть указаны любые из следующих атрибутов:
-* email (тег ФФД 1008);
-* ИНН (тег ФФД 1128);
-* наименование (тег ФФД 1127);
-* номер телефона (тег ФФД 1008).
-
-> Все эти атрибуты являются **необязательными**.
-> Если указаны одновременно и email, и номер телефона, то ОФД отправит чек только на email.
-
-Указать эти атрибуты можно двумя способами:
-
-```php
-// 1 способ - через конструктор
-$customer = new AtolOnline\Entities\Client(
- 'John Doe', // наименование
- '+1/22/99*73s dsdas654 5s6', // номер телефона +122997365456
- 'john@example.com', // email
- '+fasd3\qe3fs_=nac990139928czc' // номер ИНН 3399013928
-);
-
-// 2 способ - через сеттеры
-$customer = (new AtolOnline\Entities\Client())
- ->setEmail('john@example.com')
- ->setInn('+fasd3\q3fs_=nac9901 3928c-c') // 3399013928
- ->setName('John Doe')
- ->setPhone('+1/22/99*73s dsdas654 5s6'); // +122997365456
-
-// либо комбинация этих способов
-```
-
-Метод `setEmail()` проверяет входную строку на длину (до 64 символов) и валидность формата email.
-Выбрасывает исключения:
-* `AtolEmailTooLongException` (если слишком длинный email);
-* `AtolEmailValidateException` (если email невалиден).
-
-Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр).
-Выбрасывает исключение `AtolInnWrongLengthException` (если длина ИНН некорректна).
-
-Метод `setName()` проверяет входную строку на длину (до 256 символов).
-Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное имя).
-
-Метод `setPhone()` чистит входную строку от всех символов, кроме цифр и знака `+`, и проверяет длину (до 64 символов).
-Выбрасывает исключение `AtolPhoneTooLongException` (если слишком длинный номер телефона).
-
-Конструктор может выбрасывать любое из указанных выше исключений, если в него передаются значения.
-
-Получить установленные значения атрибутов можно через геттеры:
-
-```php
-$customer->getInn();
-$customer->getEmail();
-$customer->getName();
-$customer->getPhone();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $customer;
-$json_string = (string)$customer;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $customer->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/collection.md b/docs/collection.md
new file mode 100644
index 0000000..1c0a8b6
--- /dev/null
+++ b/docs/collection.md
@@ -0,0 +1,19 @@
+# Коллекция сущностей
+
+[Вернуться к содержанию](readme.md#toc)
+
+---
+
+Коллекциями являются объекты, способные хранить в себе [сущности](entity.md). Они унаследованы
+от `Illuminate/Support/Collection` и полностью поддерживают все
+[стандартные методы коллекций Laravel](https://laravel.com/docs/master/collections).
+
+Помимо этого, они валидируют количество и вид сущностей, которые могут хранить в себе, согласно схеме АТОЛ Онлайн API.
+
+Коллекции ведут себя аналогично самим сущностям в части приведения к массивам и json-ификации.
+
+---
+
+Читай также: [Сущность](entity.md)
+
+[Вернуться к содержанию](readme.md#toc)
diff --git a/docs/company.md b/docs/company.md
deleted file mode 100644
index 912bd59..0000000
--- a/docs/company.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# Работа с компанией (продавцом)
-
-[Вернуться к содержанию](readme.md)
-
----
-
-Объект компании инициализируется следующим образом:
-
-```php
-$customer = new AtolOnline\Entities\Company();
-```
-
-У объекта компании должны быть указаны все следующие атрибуты:
-* email (тег ФФД 1117);
-* ИНН (тег ФФД 1018);
-* тип системы налогообложения (тег ФФД 1055) - все типы перечислены в классе `AtolOnline\Constants\SnoTypes`;
-* адрес места расчётов (тег ФФД 1187) - для интернет-сервисов указывается URL с протоколом.
-
-> Все эти атрибуты являются **обязательными**.
-> Для тестового режима используйте значения ИНН и адреса места расчётов, [указанные здесь](https://online.atol.ru/files/ffd/test_sreda.txt).
-
-Указать эти атрибуты можно двумя способами:
-
-```php
-// 1 способ - через конструктор
-$company = new AtolOnline\Entities\Company(
- AtolOnline\Constants\SnoTypes::OSN, // тип СНО
- '5544332219', // номер ИНН
- 'https://v4.online.atol.ru', // адрес места расчётов
- 'company@example.com' // email
-);
-
-// 2 способ - через сеттеры
-$company = (new AtolOnline\Entities\Company())
- ->setEmail('company@example.com')
- ->setInn('5544332219')
- ->setSno(AtolOnline\Constants\SnoTypes::USN_INCOME)
- ->setPaymentAddress('https://v4.online.atol.ru');
-
-// либо комбинация этих способов
-```
-
-Метод `setEmail()` проверяет входную строку на длину (до 64 символов) и валидность формата email.
-Выбрасывает исключения:
-* `AtolEmailTooLongException` (если слишком длинный email);
-* `AtolEmailValidateException` (если email невалиден).
-
-Метод `setInn()` чистит входную строку от всех символов, кроме цифр, и проверяет длину (либо 10, либо 12 цифр).
-Выбрасывает исключение `AtolInnWrongLengthException` (если длина ИНН некорректна).
-
-Метод `setPaymentAddress()` проверяет длину (до 256 символов).
-Выбрасывает исключение `AtolPaymentAddressTooLongException` (если слишком длинный адрес места расчётов).
-
-Конструктор может выбрасывать любое из указанных выше исключений, если в него передаются параметры.
-
-Получить установленные значения параметров можно через геттеры:
-
-```php
-$company->getInn();
-$company->getEmail();
-$company->getPaymentAddress();
-$company->getSno();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $company;
-$json_string = (string)$company;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $company->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/correction_info.md b/docs/correction_info.md
deleted file mode 100644
index dc67a09..0000000
--- a/docs/correction_info.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Работа с данными коррекции
-
-[Вернуться к содержанию](readme.md)
-
----
-
-Объект для данных коррекции инициализируется следующим образом:
-
-```php
-$info = new AtolOnline\Entities\CorrectionInfo();
-```
-
-У объекта должны быть указаны все следующие обязательные атрибуты:
-* тип коррекции (тег ФФД 1173) - все типы перечислены в классе `AtolOnline\Constants\CorrectionTypes`;
-* дата документа основания для коррекции в формате `d.m.Y` (тег ФФД 1178);
-* номер документа основания для коррекции (тег ФФД 1179);
-* описание коррекции (тег ФФД 1177).
-
-Указать эти атрибуты можно двумя способами:
-
-```php
-use AtolOnline\{Entities\CorrectionInfo, Constants\CorrectionTypes};
-
-// 1 способ - через конструктор
-$info = new CorrectionInfo(
- CorrectionTypes::SELF, // тип коррекции
- '01.01.2019', // дата документа коррекции
- '12345', // номер документа коррекции
- 'test' // описание коррекции
-);
-
-// 2 способ - через сеттеры
-$info = (new CorrectionInfo())
- ->setType(CorrectionTypes::INSTRUCTION)
- ->setDate('01.01.2019')
- ->setName('test')
- ->setNumber('9999');
-
-// либо комбинация этих способов
-```
-
-Получить установленные значения атрибутов можно через геттеры:
-
-```php
-$info->getType();
-$info->getDate();
-$info->getName();
-$info->getNumber();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $customer;
-$json_string = (string)$customer;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $customer->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/documents.md b/docs/documents.md
deleted file mode 100644
index 1d92174..0000000
--- a/docs/documents.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# Работа с документами
-
-[Вернуться к содержанию](readme.md)
-
----
-
-Объект документа инициализируется следующим образом:
-
-```php
-$doc = new AtolOnline\Entities\Document();
-```
-
-Для документов **прихода, возврата прихода, расхода и возврата расхода** должны быть указаны все следующие обязательные атрибуты:
-* [клиент](/docs/client.md);
-* [компания](/docs/company.md);
-* [предметы расчёта](/docs/items.md);
-* [оплаты](/docs/payments.md).
-
-Для документов **коррекции прихода и коррекции расхода** должны быть указаны все следующие обязательные атрибуты:
-* [компания](/docs/company.md);
-* [оплаты](/docs/payments.md);
-* [ставки НДС](/docs/vats.md);
-* [данные коррекции](/docs/correction_info.md).
-
-Для любых документов также могут быть указаны следующие необязательные атрибуты:
-* ФИО кассира (тег ФФД - 1021).
-
-Установка атрибутов документа происходит через сеттеры.
-
-## Работа с клиентом
-
-Для этого существуют следующие методы:
-
-```php
-$doc->setClient($client);
-$doc->getClient();
-```
-
-> О работе с клиентами более подробно читайте [здесь](/docs/client.md).
-
-## Работа с компанией
-
-Для этого существуют следующие методы:
-
-```php
-$doc->setCompany($company);
-$doc->getCompany();
-```
-
-> О работе с компаниями более подробно читайте [здесь](/docs/company.md).
-
-## Работа с предметами расчёта
-
-Внутри документа существует [массив предметов расчёта](/docs/items.md#array).
-По умолчанию он пуст.
-Напрямую для манипуляций объект массива недоступен.
-Работа с ним происходит через методы документа:
-
-```php
-$doc->setItems([$item1, $item2]);
-$doc->addItem($item3);
-$doc->getItems();
-```
-
-Соответственно, эти методы выбрасывают те же исключения, что методы самого массива.
-
-## Работа с оплатами
-
-Внутри документа существует [массив оплат](/docs/payments.md#array).
-По умолчанию он пуст.
-Напрямую для манипуляций объект массива недоступен.
-Работа с ним происходит через методы документа:
-
-```php
-$doc->setPayments([$payment1, $payment2]);
-$doc->addPayment($payment3);
-$doc->getPayments();
-```
-
-Соответственно, эти методы выбрасывают те же исключения, что методы самого массива.
-
-Следует отметить, что если при выполнении метода `addPayment()` выполняются следующие условия:
-* аргументом передан объект оплаты, у которого не задана сумма,
-* ранее документу не задавались оплаты,
-то автоматически этому объекту оплаты задаётся полная сумма чека.
-
-## Работа со ставками НДС
-
-Внутри документа существует [массив ставок НДС](/docs/vats.md#array).
-По умолчанию он пуст.
-Напрямую для манипуляций объект массива недоступен.
-Работа с ним происходит через методы документа:
-
-```php
-$doc->setVats([$vat1, $vat2]);
-$doc->addVat($vat3);
-$doc->getVats();
-```
-
-Соответственно, эти методы выбрасывают те же исключения, что методы самого массива.
-
-Также существует метод `clearVats()`, который удаляет все вложенные объекты ставок НДС - из предметов расчёта и самого документа.
-
-Следует отметить, что если при выполнении метода `addVat()` выполняются следующие условия:
-* аргументом передан объект ставки, у которого не задана сумма,
-* ранее документу не задавались ставки,
-то автоматически этому объекту налога задаётся полная сумма чека, от которой расчитывается итоговый размер налога.
-
-## Общая сумма документа
-
-Расчёт происходит автоматически в следующих случаях:
-* изменение предметов расчёта (`setItems()`, `addItem()`);
-* добавление оплат (`addPayment()` в случае, когда оплата передана без суммы);
-* изменение ставок НДС (`setVats()`, `clearVats()`, `addVat()` в случае, когда ставка передана без суммы);
-* приведение объекта документа к строке.
-
-Также можно вызвать вручную метод `calcTotal()`.
-Он расчитывает полную сумму чека по предметам расчёта и пересчитывает **все** налоговые ставки.
-
-Получить итог можно с помощью метода `getTotal()`.
-
-Всё в рублях.
-
-
-## Работа с данными коррекции
-
-Если документ создаётся с целью коррекции прихода или расхода, то он обязательно должен содержать [данные коррекции](/docs/correction_info.md).
-
-Задать и получить эти данные очень просто:
-
-```php
-$doc->setCorrectionInfo(new AtolOnline\Entities\CorrectionInfo(
- AtolOnline\Constants\CorrectionTypes::SELF, // тип коррекции
- '01.01.2019', // дата документа коррекции
- '12345', // номер документа коррекции
- 'test' // описание коррекции
-));
-$doc->getCorrectionInfo();
-```
-
-## Работа с кассиром
-
-Для этого существуют следующие методы:
-
-```php
-$doc->setCashier('Иванова Лариса Васильевна');
-$doc->getCashier();
-```
-
-## Прочее
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $doc;
-$json_string = (string)$doc;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $doc->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
diff --git a/docs/entity.md b/docs/entity.md
new file mode 100644
index 0000000..76c79bc
--- /dev/null
+++ b/docs/entity.md
@@ -0,0 +1,52 @@
+# Сущность
+
+[Вернуться к содержанию](readme.md#toc)
+
+---
+
+Сущностями являются все классы, которые необходимы для взаимодействия с API. Они находятся в директори `src/Entities` и
+расширяют абстрактный класс `AtolOnline\Entities\Entity`.
+
+Каждая сущность содержит в себе только те данные, которые необходимы согласно схемы АТОЛ Онлайн API.
+
+Ниже перечислены возможности сущностей.
+
+## Приведение к строке JSON
+
+```php
+echo $entity;
+$json_string = (string)$entity;
+```
+
+## Приведение к массиву
+
+```php
+// результат идентичен
+$json_array1 = $entity->jsonSerialize();
+$json_array2 = $entity->toArray();
+```
+
+## Чтение из массива
+
+```php
+$var = new \AtolOnline\Entities\Client('Иванов Иван');
+echo $var['name']; // 'Иванов Иван'
+$var['name'] = 'Петров Пётр'; // BadMethodCallException
+```
+
+## Fluent-сеттеры
+
+Реализованы на уровне конкретных классов сущностей, но у некоторых могут полностью отсуствовать.
+
+```php
+$entity->setFoo($value)->setBar('bar')->...
+```
+
+Сеттеры валидируют и фильтруют данные согласно схеме АТОЛ Онлайн API, а в случае ошибочных значений -- выбрасывают
+исключения.
+
+---
+
+Читай также: [Коллекция сущностей](collection.md)
+
+[Вернуться к содержанию](readme.md#toc)
diff --git a/docs/fiscalizing.md b/docs/fiscalizing.md
new file mode 100644
index 0000000..042d4be
--- /dev/null
+++ b/docs/fiscalizing.md
@@ -0,0 +1,237 @@
+# Фискализация документов
+
+[Вернуться к содержанию](readme.md#toc)
+
+---
+
+## Доступ к ККТ
+
+Для работы с облачной ККТ необходимы следующие параметры:
+
+* логин;
+* пароль;
+* код группы.
+
+Чтоы получить их, нужно:
+
+1. авторизоваться в личном кабинете [online.atol.ru](https://online.atol.ru/lk/Account/Login);
+2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**.
+ Скачается XML-файл с нужными настройками.
+
+Также для работы потребуются:
+
+* ИНН продавца;
+* URL места расчёта (ссылка на ваш интернет-сервис).
+
+## Использование
+
+Объект ККТ инициализируется следующим образом:
+
+```php
+$kkt = new AtolOnline\Api\Fiscalizer();
+```
+
+Установить параметры подключения можно двумя путями:
+
+```php
+use AtolOnline\Api\Fiscalizer;
+
+// 1 способ - через конструктор
+$kkt = new Fiscalizer(group: 'mygroup', login: 'mylogin', password: 'mypassword');
+
+// 2 способ - через сеттеры
+$kkt = (new Fiscalizer())
+ ->setLogin($login)
+ ->setGroup($group)
+ ->setPassword($password);
+```
+
+
+
+## Тестовый режим
+
+По умолчанию фискализатор создаётся для работы в тестовом режиме. Это означает, что работа с АТОЛ Онлайн API будет
+происходить [в тестовой среде](https://online.atol.ru/files/ffd/test_sreda.txt).
+
+> Под тестовым режимом работы подразумевается использование тестовых ККТ, которые принадлежат компании АТОЛ.
+
+Управление тестовым режимом происходит следующим образом:
+
+```php
+$kkt = new Fiscalizer(); // включен по умолчанию
+$kkt = new Fiscalizer(false); // выключен явно
+$kkt->setTestMode(); // включен явно
+$kkt->setTestMode(true); // включен явно
+$kkt->setTestMode(false); // выключен явно
+```
+
+**При включенном тестовом режиме используются тестовые ККТ**, т.к. перед отправкой запроса подменяются:
+
+* логин;
+* пароль;
+* группа ККТ;
+* ИНН клиента (покупателя);
+* ИНН и адрес места расчётов компании (продавца).
+
+Таким образом:
+
+* использовать тестовый режим -- безопасно;
+* при переключении тестового режима устанавливать заново свои параметры подключения не требуется.
+
+**При выключенном тестовом режиме используются ваши ККТ.**
+
+Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом
+режиме (на собственной ККТ). В этом случае важно понимать следующее:
+
+1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**;
+2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html);
+3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах;
+4. вся ответственность за пп. 1-3 и последствия ложится только на вас.
+
+## Авторизация на ККТ
+
+Перед первым запросом на ККТ происходит аутентификация на сервере по логину и паролю. В ответ приходит авторизационный
+токен, срок жизни коего равен **24 часам**. После первой успешной операции возможно получить этот токен следующим
+образом:
+
+```php
+$kkt->getToken(); // вернёт строку длиной 128 символа
+```
+
+Этот токен можно сохранить и переиспользовать в течение всего срока его жизни, но далее следует получить новый токен.
+
+Ранее полученный токен следует указывать до отправки запросов следующим образом:
+
+```php
+$kkt->setToken($token_string);
+```
+
+Если токен был установлен перед выполнением операции, то при выполнении операции будет использоваться именно он. Если
+операция завершится ошибочно из-за истёкшего токена, следует повторить операцию без использования метода `setToken()`,
+либо обнулив его следующим образом:
+
+```php
+$kkt->setToken(null);
+```
+
+Тогда будет получен новый токен.
+
+## Регистрация документа
+
+Для регистрации документа **прихода** необходимо вызвать метод `sell()`:
+
+```php
+$result = $kkt->sell($document);
+$result2 = $receipt->sell($kkt);
+```
+
+Для регистрации документа **возврата прихода** необходимо вызвать метод `sellRefund()`:
+
+```php
+$result = $kkt->sellRefund($document);
+$result2 = $receipt->sellRefund($kkt);
+```
+
+Для регистрации документа **расхода** необходимо вызвать метод `buy()`:
+
+```php
+$result = $kkt->buy($document);
+$result2 = $receipt->buy($kkt);
+```
+
+Для регистрации документа **возврата расхода** необходимо вызвать метод `buyRefund()`:
+
+```php
+$result = $kkt->buyRefund($document);
+$result2 = $receipt->buyRefund($kkt);
+```
+
+Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellCorrection()`:
+
+```php
+$result = $kkt->sellCorrect($document);
+$result2 = $correction->sellCorrect($kkt);
+```
+
+Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyCorrection()`:
+
+```php
+$result = $kkt->buyCorrect($document);
+$result2 = $correction->buyCorrect($kkt);
+```
+
+### Собственный идентификатор документа (`external_id`)
+
+Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
+документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки. По умолчанию это UUID версии 4.
+
+Чтобы использовать собственный идентификатор, следует передать нужное строковое значение вторым параметром в любой из
+шести описанных выше методов, например:
+
+```php
+$result = $kkt->sellRefund($document, 'order_' . $order->id);
+```
+
+Если `external_id` не указан явно или имеет пустое значение, то будет сгенерирован новый UUID. Узнать его можно будет
+только в ответе от ККТ после регистрации документа в очереди на фискализацию.
+
+### Передача `callback_url`
+
+Перед регистрацией документа можно указать `callback_url`. АТОЛ отправит на указанный URL результат регистрации. По
+этому адресу должен располагаться ваш собственный обработчик статуса фискализации.
+
+```php
+$kkt->setCallbackUrl('http://example.com/process-kkt-result');
+$kkt->getCallbackUrl();
+```
+
+## Проверка статуса документа
+
+Если перед отправкой документа на регистрацию был задан `callback_url` через метод `setCallbackUrl()`, то ответ придёт
+на указанный адрес автоматически, как только документ обработается на стороне ККТ. Ответ может быть как об успешной
+регистрации, так и ошибочной.
+
+В любом случае, вам доступны два метода, с помощью которых вы можете проверять статус документа самостоятельно:
+
+```php
+$kkt->getDocumentStatus(); // делает единичный запрос
+$kkt->pollDocumentStatus(); // делает запросы до получения конечного статуса (не-wait)
+```
+
+Эти методы принимают на вход `uuid` кода регистрации. Этот UUID нужно взять из ответа, полученного при отправке
+документа на регистрацию:
+
+```php
+$sell_result = $kkt->sell($document);
+$status = $kkt->pollDocumentStatus($sell_result->uuid);
+```
+
+Метод `pollDocumentStatus()` многократно опрашивает ККТ на предмет состояния документа. Метод может принимать до трёх
+параметров:
+
+* uuid;
+* количество попыток (по умолчанию — 5);
+* время между попытками в секундах (по умолчанию — 1).
+
+```php
+// Проверять статус 10 раз на протяжении 20 секунд — каждые две секунды
+$kkt->pollDocumentStatus($sell_result->uuid, 10, 20);
+```
+
+Учитывайте, что метод вернёт результат как только сменится статус регистрации на успешный `done` или ошибочный `error`.
+
+Использовать его лучше сразу после отправки документа на регистрацию (как в примере выше).
+
+> Как правило, фискализация одного документа занимает 4-6 секунд с учётом регистрации.
+
+Метод `getDocumentStatus()` принимает на вход только `uuid` и запрашивает состояние документа лишь единожды.
+Использовать его целесообразнее в те моменты, когда нет необходимости знать успех регистрации сразу после отправки
+документа.
+
+> Обратите внимание, что АТОЛ позволяет получать статус документа в течение 32 суток с момента его регистрации.
+
+---
+
+Читай также: [Обработка ответа API](response.md)
+
+[Вернуться к содержанию](readme.md#toc)
diff --git a/docs/items.md b/docs/items.md
deleted file mode 100644
index 730a1e8..0000000
--- a/docs/items.md
+++ /dev/null
@@ -1,202 +0,0 @@
-# Работа с предметами расчёта
-
-[Вернуться к содержанию](readme.md)
-
----
-
-## Один объект
-
-Объект предмета расчёта инициализируется следующим образом:
-
-```php
-$vat = new AtolOnline\Entities\Item();
-```
-
-У объекта предмета расчёта должны быть указаны все следующие обязательные атрибуты:
-* наименование (тег ФФД - 1030);
-* цена (тег ФФД - 1079);
-* количество, вес (тег ФФД - 1023).
-
-У объекта предмета расчёта также могут быть указаны следующие необязательные атрибуты:
-* единица измерения количества (тег ФФД - 1197);
-* признак способа оплаты (тег ФФД - 1214) - перечислены в классе `AtolOnline\Constants\PaymentMethods`;
-* признак предмета расчёта (тег ФФД - 1212) - перечислены в классе `AtolOnline\Constants\PaymentObjects`;
-* [ставка НДС](/docs/vats.md);
-* дополнительный реквизит (тег ФФД - 1191).
-
-Установить многие (но не все) атрибуты можно следующими способами:
-
-```php
-use AtolOnline\{
- Constants\PaymentMethods,
- Constants\PaymentObjects,
- Constants\VatTypes,
- Entities\Item
-};
-
-// 1 способ - через конструктор
-$item = new Item(
- 'Банан', // наименование
- 100, // цена
- 1, // количество, вес
- 'кг', // единица измерения
- VatTypes::VAT20, // ставка НДС
- PaymentObjects::SERVICE, // признак предмета расчёта
- PaymentMethods::FULL_PAYMENT // признак способа расчёта
-);
-
-// 2 способ - через сеттеры
-$item = new Item();
-$item->setName('Банан');
-$item->setPrice(100);
-$item->setQuantity(2.41);
-//$item->setQuantity(2.41, 'кг');
-$item->setMeasurementUnit('кг');
-$item->setVatType(VatTypes::VAT20);
-$item->setPaymentObject(PaymentObjects::COMMODITY);
-$item->setPaymentMethod(PaymentMethods::FULL_PAYMENT);
-```
-
-Метод `setName()` проверяет входную строку на длину (до 128 символов).
-Выбрасывает исключение `AtolNameTooLongException` (если слишком длинное наименование).
-
-Метод `setPrice()` проверяет аргумент на величину (до 42949672.95) и пересчитывает общую стоимость.
-Выбрасывает исключение `AtolPriceTooHighException` (если цена слишком высока).
-
-Метод `setMeasurementUnit()` проверяет входную строку на длину (до 16 символов).
-Выбрасывает исключение `AtolUnitTooLongException` (если слишком длинная строка единицы измерения).
-
-Метод `setQuantity()` проверяет первый аргумент на величину (до 99999.999) и пересчитывает общую стоимость.
-Выбрасывает исключения:
-* `AtolQuantityTooHighException` (если количество слишком велико);
-* `AtolPriceTooHighException` (если общая стоимость слишком велика).
-
-Также вторым аргументом может принимать единицу измерения количества.
-В этом случае дополнительно работает сеттер `setMeasurementUnit()`.
-
-Метод `setVatType()` задаёт тип ставки НДС, пересчитывает размер налога и общую стоимость.
-Выбрасывает исключение `AtolPriceTooHighException` (если цена слишком высока).
-Может принимать `null` для удаления налога.
-
-Дополнительный реквизит устанавливается отдельным методом `setUserData()`:
-
-```php
-$item->setUserData('some data');
-```
-
-Он проверяет строку на длину (до 64 символов).
-Выбрасывает исключение `AtolUserdataTooLongException` (если слишком длинный дополнительный реквизит).
-
-Для установки признака предмета расчёта существует метод `setPaymentObject()`.
-На вход следует передавать одной из значений, перечисленных в классе `AtolOnline\Constants\PaymentObjects`.
-
-```php
-$item->setPaymentObject(AtolOnline\Constants\PaymentObjects::JOB);
-```
-
-Для установки признака способа оплаты существует метод `setPaymentMethod()`.
-На вход следует передавать одной из значений, перечисленных в классе `AtolOnline\Constants\PaymentMethods`.
-
-```php
-$item->setPaymentMethod(AtolOnline\Constants\PaymentMethods::FULL_PAYMENT);
-```
-
-Для получения заданных значений атрибутов реализованы соответствующие геттеры:
-
-```php
-$item->getName();
-$item->getPrice();
-$item->getQuantity();
-$item->getMeasurementUnit();
-$item->getPaymentMethod();
-$item->getPaymentObject();
-$item->getVat(); // возвращает объект ставки НДС либо null
-$item->getUserData();
-```
-
-Для пересчёта общей стоимости и размера налога существует метод `calcSum()`.
-
-```php
-$item->calcSum();
-```
-
-Этот метод отрабатывает при вызове `setPrice()`, `setQuantity()` и `setVatType()`.
-Выбрасывает исключение `AtolPriceTooHighException` (если общая сумма слишком высока).
-
-Получить уже расчитанную общую сумму можно простым геттером:
-
-```php
-$item->getSum();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $item;
-$json_string = (string)$item;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $item->jsonSerialize();
-```
-
-
-## Массив объектов предметов расчёта
-
-> Максимальное количество объектов в массиве - 100.
-
-Массив инициализируется следующим образом:
-
-```php
-$item_array = new AtolOnline\Entities\ItemArray();
-```
-
-Чтобы задать содержимое массива, используйте метод `set()`:
-
-```php
-$item_array->set([
- $item_object1,
- $item_object2
-]);
-```
-
-Очистить его можно передачей в сеттер пустого массива:
-
-```php
-$item_array->set([]);
-```
-
-Чтобы добавить объект к существующим элементам массива, используйте метод `add()`:
-
-```php
-$item = new AtolOnline\Entities\Item('Банан', 100, 1);
-$item_array->add($item);
-```
-
-Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением.
-Выбрасывают исключение `AtolTooManyItemsException` (если в массиве уже максимальное количество объектов).
-
-Чтобы получить содержимое массива, используйте метод `get()`:
-
-```php
-$item_array->get();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $item_array;
-$json_string = (string)$item_array;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $item_array->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/kkt.md b/docs/kkt.md
deleted file mode 100644
index 060a364..0000000
--- a/docs/kkt.md
+++ /dev/null
@@ -1,296 +0,0 @@
-# Работа с ККТ
-
-[Вернуться к содержанию](readme.md)
-
----
-
-Объект ККТ инициализируется следующим образом:
-
-```php
-$kkt = new AtolOnline\Api\Kkt();
-```
-
-## Настройка ККТ
-
-Для работы с облачной ККТ необходимы следующие параметры:
-* логин кассы;
-* пароль кассы;
-* код группы кассы;
-
-Чтоб получить их, нужно:
-1. авторизоваться на [online.atol.ru](https://online.atol.ru/lk/Account/Login);
-2. на странице [Мои компании](https://online.atol.ru/lk/Company/List) нажать кнопку **Настройки интегратора**.
- Скачается XML-файл с нужными настройками.
-
-Установить эти параметры можно двумя путями:
-
-```php
-// 1 способ - через конструктор
-$kkt = new AtolOnline\Api\Kkt($group, $login, $password);
-
-// 2 способ - через сеттеры
-$kkt = (new AtolOnline\Api\Kkt())
- ->setLogin($login)
- ->setGroup($group)
- ->setPassword($password);
-```
-
-Получить заданные параметры можно через соответствующие геттеры:
-
-```php
-$kkt->getLogin();
-$kkt->getPassword();
-$kkt->getGroup();
-```
-
-Также для работы потребуются:
-* ИНН продавца;
-* URL места расчёта (ссылка на ваш интернет-сервис).
-
-Эти параметры нужно задать [объекту компании](/docs/company.md), который будет передаваться в документах через эту ККТ.
-
-
-## Тестовый режим
-
-На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе.
-АТОЛ предоставляет нам отдельную тестовую среду (ККТ).
-[Её настройки](https://online.atol.ru/files/ffd/test_sreda.txt) уже указаны в коде библиотеки.
-*Под тестовым режимом работы подразумевается использование другой (тестовой) ККТ.*
-
-При включенном тестовом режиме:
-* меняется логин, пароль и группа (для обращения на тестовую ККТ)
-* между авторизацией и операцией над документом, в `Company` документа переопределяется ИНН, СНО и адрес места
-расчётов на те, что указаны в [параметрах тестовой среды](https://online.atol.ru/files/ffd/test_sreda.txt).
-
-В библиотеке есть переключатель настроек ККТ.
-С его помощью можете поменять вашу боевую ККТ на тестовую и наоборот.
-Это можно сделать одним из следующих способов:
-
-```php
-// включить в любом месте кода:
-$kkt->setTestMode();
-$kkt->setTestMode(true);
-$kkt->setTestMode(false); // выключить
-```
-
-> Если вы включили тестовый режим (как показано выше), то используются именно эта ККТ, а не ваша.
-> После выключения тестового режима настройки доступа к ККТ меняются на ваши (используется уже ваша ККТ).
-
-Для включения тестового режима необязательно задавать параметры боевой ККТ.
-
-Если по каким-то причинам у вас не получится использовать тестовый режим, вы можете проводить свои тесты в боевом
-режиме (на собственной ККТ).
-В этом случае важно понимать следующее:
-1. сразу после оформления документа **прихода** необходимо оформлять точно такой же документ **возврата прихода**;
-2. [вы обязательно забудете о пункте 1](http://murphy-law.net.ru/basics.html);
-3. пп. 1 и 2 в любом случае скажутся на ваших финансовых отчётах;
-4. вся ответственность за пп. 1-3 и последствия ложится только на вас.
-
-## Авторизация на ККТ
-
-Перед первым запросом на ККТ происходит аутентификация на сервере по логину и паролю.
-В ответ приходит авторизационный токен, срок жизни коего равен **24 часам**.
-После первой успешной операции возможно получить этот токен следующим образом:
-
-```php
-$kkt->getAuthToken(); // вернёт строку длиной 128 символа
-```
-
-Этот токен можно сохранить и переиспользовать в течение всего срока его жизни.
-Спустя это время следует получить новый токен.
-
-Для дальнейшего использования однажды полученный токен следует указывать следующим образом:
-
-```php
-$kkt->setAuthToken($token_string);
-```
-
-Если токен был установлен перед выполнением операции, то при выполнении операции будет использоваться именно он, а новый
-запрашиваться не будет. Если операция завершится ошибочно из-за истёкшего токена, следует повторить операцию без
-использования метода `setAuthToken()`, либо обнулив его следующим образом:
-
-```php
-$kkt->setAuthToken(null);
-```
-
-## Регистрация документа
-
-Для регистрации документа **прихода** необходимо вызвать метод `sell()`:
-
-```php
-$result = $kkt->sell($document);
-```
-
-Для регистрации документа **возврата прихода** необходимо вызвать метод `sellRefund()`:
-
-```php
-$result = $kkt->sellRefund($document);
-```
-
-Для регистрации документа **расхода** необходимо вызвать метод `buy()`:
-
-```php
-$result = $kkt->buy($document);
-```
-
-Для регистрации документа **возврата расхода** необходимо вызвать метод `buyRefund()`:
-
-```php
-$result = $kkt->buyRefund($document);
-```
-
-Для операций, перечисленных выше, документы не должны содержать [данных коррекции](/docs/documents.md#correction).
-Тогда как для операций коррекции, которые описаны ниже, эти данные должны присутствовать.
-
-Для регистрации документа **коррекции прихода** необходимо вызвать метод `sellCorrection()`:
-
-```php
-$result = $kkt->sellCorrection($document);
-```
-
-Для регистрации документа **коррекции расхода** необходимо вызвать метод `buyCorrection()`:
-
-```php
-$result = $kkt->buyCorrection($document);
-```
-
-Любой из перечисленных выше шести методов может выбросить исключение `AtolAuthFailedException` при ошибке
-аутентификации или авторизации.
-
-### Собственный идентификатор документа
-
-Каждый документ, переданный на ККТ для регистрации, всегда имеет свой идентификатор, абсолютно уникальный среди всех
-документов когда-либо регистрировавшихся на ККТ, даже если при регистрации были ошибки.
-По умолчанию это UUID версии 4.
-
-Чтобы использовать собственный идентификатор, следует передать нужное строковое значение вторым параметром в любой из
-шести описанных выше методов, например:
-
-```php
-$kkt->sell($document, $my_unique_id);
-$kkt->sellRefund($document, $my_unique_id);
-$kkt->buy($document, $my_unique_id);
-$kkt->buyRefund($document, $my_unique_id);
-$kkt->sellCorrection($document, $my_unique_id);
-$kkt->buyCorrection($document, $my_unique_id);
-```
-
-Если `$my_unique_id` равен `null` или пустой строке, то будет сгенерирован новый UUID.
-Узнать его можно будет только в ответе от ККТ.
-
-### Передача callback_url
-
-Перед регистрацией документа можно указать `callback_url`.
-АТОЛ отправит на указанный URL результат регистрации.
-По этому адресу должен располагаться код, который будет обрабатывать этот результат.
-
-```php
-$kkt->setCallbackUrl('http://example.com/process-kkt-result');
-$kkt->getCallbackUrl();
-```
-
-Метод `setCallbackUrl()` проверяет входную строку на длину (до 256 символов) и валидность формата url по
-регулярному выражению:
-
-```
-^http(s?)\:\/\/[0-9a-zA-Zа-яА-Я]([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-\.\?\,\'\/\\\+&=%$#_]*)?$
-```
-
-Выбрасывает исключения:
-* `AtolCallbackUrlTooLongException` (если слишком длинный url);
-* `AtolInvalidCallbackUrlException` (если url невалиден).
-
-## Обработка результата регистрации
-
-Методы `sell()`, `sellRefund()`, `sellCorrection()`, `buy()`, `buyRefund()` и `buyCorrection()` возвращают объект `AtolOnline\Api\KktResponse`.
-
-Этот же объект можно получить через геттер `getLastResponse()`.
-
-Этот объект содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа.
-
-```php
-$result = $kkt->getLastResponse(); // вернёт последний ответ от API
-$headers = $result->getHeaders(); // вернёт заголовки
-$code = $result->getCode(); // вернёт код ответа
-$body = $result->getContent(); // вернёт JSON-декодированное тело ответа
-```
-
-Обращаться к полям JSON-декодированного объекта можно опуская вызов метода `getContent()` таким образом:
-
-```php
-// вернёт значение поля uuid
-$uuid = $result->getContent()->uuid;
-$uuid = $result->uuid;
-// вернёт текст ошибки
-$err_text = $result->getContent()->error->text;
-$err_text = $result->error->text;
-```
-
-Проверка корректности ответа (отсутствия ошибок) работает через метод `isValid()`:
-
-```php
-$kkt->getLastResponse()->isValid(); // вернёт true, если ошибок нет
-```
-
-## Проверка статуса документа
-
-Если перед отправкой документа на регистрацию был задан callback_url через метод `setCallbackUrl()`, то ответ придёт на указанный адрес автоматически, как только документ обработается на стороне ККТ.
-Ответ может быть как об успешной регистрации, так и ошибочной.
-
-В любом случае, вам доступны два метода, с помощью которых вы можете проверять статус документа самостоятельно:
-
-```php
-$kkt->getDocumentStatus();
-$kkt->pollDocumentStatus();
-```
-
-Эти методы принимают на вход `uuid` кода регистрации.
-Этот UUID можно получить из ответа, полученного при отправке документа на регистрацию:
-
-```php
-$sell_result = $kkt->sell($document);
-$kkt->pollDocumentStatus($sell_result->uuid);
-$kkt->getDocumentStatus($sell_result->uuid);
-```
-
-Метод `pollDocumentStatus()` многократно опрашивает ККТ на предмет состояния документа.
-Метод может принимать до трёх параметров:
-* uuid;
-* количество попыток (по умолчанию — 5);
-* время между попытками в секундах (по умолчанию — 1).
-
-```php
-// Проверять статус 10 раз на протяжении 20 секунд — каждые две секунды
-$kkt->pollDocumentStatus($sell_result->uuid, 10, 20);
-```
-
-Учитывайте, что метод вернёт результат как только сменится статус регистрации на успешный или ошибочный.
-
-Использовать его лучше сразу после отправки документа на регистрацию (как в примере выше).
-
-> Как правило, полная регистрация одного документа занимает 4-5 секунд.
-
-Метод `getDocumentStatus()` принимает на вход только `uuid` и запрашивает состояние документа лишь единожды.
-Использовать его целесообразнее в те моменты, когда нет необходимости знать успех регистрации сразу после отправки документа.
-
-> Обратите внимание, что АТОЛ позволяет получать статус документа в течение 32 суток с момента его регистрации.
-
-Методы `pollDocumentStatus()` и `getDocumentStatus()` возвращают объект `AtolOnline\Api\KktResponse`.
-Оба выбрасывают исключение `AtolUuidValidateException` (если переданная строка UUID невалидна).
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $item;
-$json_string = (string)$item;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $item->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/monitoring.md b/docs/monitoring.md
new file mode 100644
index 0000000..3bb81ce
--- /dev/null
+++ b/docs/monitoring.md
@@ -0,0 +1,118 @@
+# Мониторинг ККТ
+
+[Вернуться к содержанию](readme.md#toc)
+
+---
+
+Библиотека предоставляет возможность следить за состоянием ваших облачных ККТ через API.
+
+## Инициализация
+
+Для этого следует использовать класс `KktMonitor`:
+
+```php
+// можно передать параметры подключения в конструктор
+$monitor = new AtolOnline\Api\Monitor(
+ login: 'mylogin',
+ password: 'qwerty'
+);
+
+// можно - отдельными сеттерами
+$monitor = new AtolOnline\Api\Monitor();
+ ->setLogin($credentials['login'])
+ ->setPassword($credentials['password']);
+```
+
+Логин и пароль для мониторинга те же, что для регистрации документов.
+
+**По умолчанию монитор работает в тестовом режиме.**
+Перевести его в боевой режим можно:
+
+```php
+// передачей в конструктор `false` первым параметром:
+$monitor = new AtolOnline\Api\Monitor(false, /*...*/);
+
+// или отдельным сеттером
+$monitor->setTestMode(false);
+```
+
+**Тестовый режим** нужен для проверки работоспособности библиотеки и API АТОЛ.
+
+**В боевом режиме** можно получать данные по своим ККТ.
+
+## Получение данных обо всех своих ККТ
+
+Для получения данных обо всех своих ККТ следует вызвать метод `AtolOnline\Api\KktMonitor::getAll()`:
+
+```php
+$kkts = $monitor->getAll();
+```
+
+В ответе будет итерируемая коллекция объектов `AtolOnline\Entities\Kkt`. Каждый из этих объектов содержит атрибуты:
+
+```php
+// для примера получим первую ККТ из всех
+$kkt = $kkts->first();
+
+// посмотрим на её атрибуты:
+$kkt->serialNumber; // Заводской номер ККТ
+$kkt->registrationNumber; // Регистрационный номер машины (РНМ)
+$kkt->deviceNumber; // Номер автоматического устройства (внутренний идентификатор устройства)
+$kkt->fiscalizationDate; // Дата активации (фискализации) ФН с указанием таймзоны
+$kkt->fiscalStorageExpiration; // Дата замены ФН (Срок действия ФН), с указанием таймзоны
+$kkt->signedDocuments; // Количество подписанных документов в ФН
+$kkt->fiscalStoragePercentageUse; // Наполненость ФН в %
+$kkt->fiscalStorageINN; // ИНН компании (указанный в ФН)
+$kkt->fiscalStorageSerialNumber; // Заводской (серийный) номер ФН
+$kkt->fiscalStoragePaymentAddress; // Адрес расчёта, указанный в ФН
+$kkt->groupCode; // Код группы кассы
+$kkt->timestamp; // Время и дата формирования данных, UTC
+$kkt->isShiftOpened; // Признак открыта смена (true) или закрыта (false)
+$kkt->shiftNumber; // Номер смены (или "Номер закрытой смены", когда смена закрыта)
+$kkt->shiftReceipt; // Номер документа за смену (или "Кол-во чеков закрытой смены", когда смена закрыта)
+$kkt->unsentDocs; // Количество неотправленных документов. Указывается, если значение отлично от 0.
+$kkt->firstUnsetDocTimestamp; // Дата первого неотправленного документа. Указывается, если есть неотправленные документы.
+$kkt->networkErrorCode; // Код ошибки сети
+```
+
+Эти поля описаны в документации мониторинга на [стр. 11](https://online.atol.ru/files/API_service_information.pdf)
+
+Сопоставления кодов ошибок и их описаний доступны в массиве `AtolOnline\Entities\Kkt::ERROR_CODES`.
+
+Объект класса приводится к JSON-строке автоматически или принудительно:
+
+```php
+echo $kkt;
+$json_string = (string)$kkt;
+```
+
+Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
+
+```php
+$json_array = $kkt->jsonSerialize();
+```
+
+## Получение данных об одной из своих ККТ
+
+Для этого следует вызвать метод `AtolOnline\Api\KktMonitor::getOne()`, передав на вход серийный номер (`serialNumber`)
+нужной ККТ:
+
+```php
+$kkt = $monitor->getOne($kkts->first()->serialNumber);
+```
+
+Метод вернёт единственный объект `AtolOnline\Entities\Kkt` с атрибутами, описанными выше.
+
+## Получение последнего ответа от сервера
+
+Класс `AtolOnline\Api\KktMonitor` расширяет абстрактный класс `AtolOnline\Api\AtolClient`.
+
+Это значит, что последний ответ от API АТОЛ всегда сохраняется объектом класса `AtolOnline\Api\AtolReponse`. К нему
+можно обратиться через метод `AtolOnline\Api\KktMonitor::getResponse()`, независимо от того, что возвращают другие
+методы монитора.
+
+---
+
+Читай также: [Обработка ответа API](response.md)
+
+[Вернуться к содержанию](readme.md#toc)
diff --git a/docs/payments.md b/docs/payments.md
deleted file mode 100644
index 9829a57..0000000
--- a/docs/payments.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Работа с оплатами
-
-[Вернуться к содержанию](readme.md)
-
----
-
-## Один объект
-
-Объект оплаты инициализируется следующим образом:
-
-```php
-$payment = new AtolOnline\Entities\Payment();
-```
-
-У объекта оплаты должны быть указаны все следующие атрибуты:
-* тип оплаты (теги ФФД: 1031, 1081, 1215, 1216, 1217) - все типы перечислены в классе `AtolOnline\Constants\PaymentTypes` (по умолчанию `ELECTRON`)
-* сумма оплаты (теги ФФД: 1031, 1081, 1215, 1216, 1217; по умолчанию 0)
-
-> Все эти атрибуты являются **обязательными**.
-
-Установить атрибуты можно следующими способами:
-
-```php
-// 1 способ - через конструктор
-$payment = new AtolOnline\Entities\Payment(
- AtolOnline\Constants\PaymentTypes::OTHER, // тип оплаты
- 123.45 // сумма оплаты
-);
-
-// 2 способ - через сеттер
-$payment = (new AtolOnline\Entities\Payment())
- ->setType(AtolOnline\Constants\PaymentTypes::OTHER) // тип оплаты
- ->setSum(123.45); // сумма оплаты
-```
-
-Размер налога высчитывается автоматически из общей суммы.
-Сумму, от которой нужно расчитать размер налога, можно передать следующими способами:
-
-```php
-// 1 способ - через конструктор
-$payment = new AtolOnline\Entities\Payment(
- AtolOnline\Constants\PaymentTypes::CASH, // тип оплаты
- 1234.56 // сумма оплаты в рублях
-);
-
-// 2 способ - через сеттер
-$payment = (new AtolOnline\Entities\Payment())
- ->setType(AtolOnline\Constants\PaymentTypes::CASH) // тип оплаты
- ->setSum(1234.56); // сумма оплаты в рублях
-```
-
-Получить установленную сумму оплаты в рублях можно через геттер `getSum()`:
-
-```php
-var_dump($payment->getSum());
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $payment;
-$json_string = (string)$payment;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $payment->jsonSerialize();
-```
-
-
-## Массив объектов оплат
-
-> Максимальное количество объектов в массиве - 10.
-
-Массив инициализируется следующим образом:
-
-```php
-$payment_array = new AtolOnline\Entities\PaymentArray();
-```
-
-Чтобы задать содержимое массива, используйте метод `set()`:
-
-```php
-use AtolOnline\{Constants\PaymentTypes, Entities\Payment};
-
-$payment_array->set([
- new Payment(PaymentTypes::ELECTRON, 123),
- new Payment(PaymentTypes::ELECTRON, 53.2),
- new Payment(PaymentTypes::ELECTRON, 23.99),
- new Payment(PaymentTypes::ELECTRON, 11.43)
-]);
-```
-
-Очистить его можно передачей в сеттер пустого массива:
-
-```php
-$payment_array->set([]);
-```
-
-Чтобы добавить объект к существующим элементам массива, используйте метод `add()`:
-
-```php
-use AtolOnline\{Constants\PaymentTypes, Entities\Payment};
-
-$payment = new Payment(PaymentTypes::PRE_PAID, 20);
-$payment_array->add($payment);
-```
-
-Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением.
-Выбрасывают исключение `AtolTooManyPaymentsException` (если в массиве уже максимальное количество объектов).
-
-Чтобы получить содержимое массива, используйте метод `get()`:
-
-```php
-$payment_array->get();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $payment_array;
-$json_string = (string)$payment_array;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $payment_array->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/docs/readme.md b/docs/readme.md
index 0887090..9b95680 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -1,15 +1,49 @@
-# Документация к библиотеке atol-online
+# Документация к библиотеке
-Содержание:
-1. [Работа с клиентами (покупателями)](client.md)
-2. [Работа с компанией (продавцом)](company.md)
-3. [Работа с оплатами](payments.md)
-4. [Работа со ставками НДС](vats.md)
-5. [Работа с предметами расчёта](items.md)
-6. [Работа с данными коррекции](correction_info.md)
-7. [Работа с документами](documents.md)
-8. [Работа с ККТ](kkt.md)
+
----
+## Содержание
-Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.
+* [Общий алгоритм](#getstarted)
+* [Сущность](entity.md)
+* [Коллекция сущностей](collection.md)
+* [Мониторинг ККТ](monitoring.md)
+* [Фискализация документа](fiscalizing.md)
+* [Обработка ответа API](response.md)
+
+Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.
+
+
+
+## Общий алгоритм
+
+1. Создать документ `AtolOnline\Entities\Receipt` или `AtolOnline\Entities\Correction`, добавив в него все необходимые
+ данные
+2. Отправить документ на регистрацию:
+ 2.1. *Необязательно:* при отправке задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа;
+ 2.2. *Необязательно:* при отправке задать `external_id`, чтобы присвоить свой уникальный идентификатор документа;
+3. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации.
+ > Если с документом был передан `callback_url`, то ответ придёт на этот самый URL.
+ > Он должен быть обработан вашим сервисом в соответствии с бизнес-процессом.
+
+ > Если с документом **не был** передан `callback_url` **либо** callback от АТОЛа не пришёл в течение 300 секунд
+ > (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа.
+4. Проверить состояние документа:
+ 4.1. взять `uuid` ответа, полученного на запрос фискализации;
+ 4.2. отправить его в запросе состояния документа.
+ > Данные о документе можно получить только в течение 32 суток после успешной фискализации.
+
+В зависимости от специфики бизнеса, в документах можно/нужно передавать разную информацию. Подробности в документации
+АТОЛ Онлайн и исходниках библиотеки.
+
+## Об отправке электронного чека покупателю
+
+После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в
+связке с вашей ККТ:
+
+* **по email**, если в документе указан email клиента;
+* **по смс**:
+ * если в документе указан номер телефона клиента;
+ * если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД).
+
+> Если заданы email и телефон, то ОФД отдаёт приоритет email.
diff --git a/docs/response.md b/docs/response.md
new file mode 100644
index 0000000..f13ec8d
--- /dev/null
+++ b/docs/response.md
@@ -0,0 +1,57 @@
+# Обработка ответа API
+
+[Вернуться к содержанию](readme.md#toc)
+
+---
+
+Объект класса `AtolOnline\Api\AtolResponse` возвращается всеми методами, которые обращаются к АТОЛ Онлайн API.
+
+Поскольку классы `AtolOnline\Api\Fiscalizer` и `AtolOnline\Api\KktMonitor` наследуются от
+абстрактного `AtolOnline\Api\AtolClient`, они оба предоставляют метод `getLastReponse()` для возврата последнего
+полученного ответа от API.
+
+Таким образом, а общем случае необязательно сразу сохранять ответ в переменную:
+
+```php
+$response = $kkt->sell($receipt);
+```
+
+Достаточно обратиться к нему позднее:
+
+```php
+$kkt->sell($receipt);
+// ...
+$response = $kkt->getLastResponse();
+```
+
+Однако, при сложной логике и многократных запросах следует пользоваться этим с умом и осторожностью.
+
+Объект `AtolResponse` содержит в себе HTTP-код ответа, массив заголовков и JSON-декодированные данные тела ответа.
+
+```php
+$headers = $response->getHeaders(); // вернёт заголовки
+$code = $response->getCode(); // вернёт код ответа
+$body = $response->getContent(); // вернёт JSON-декодированный объект тела ответа
+```
+
+Обращаться к полям тела ответа можно опуская вызов метода `getContent()`:
+
+```php
+// вернёт значение поля uuid
+$uuid = $response->getContent()->uuid;
+$uuid = $response->uuid;
+
+// вернёт текст ошибки
+$err_text = $response->getContent()->error->text;
+$err_text = $response->error->text;
+```
+
+Проверка успешности операции доступна через метод `isSuccessful()`:
+
+```php
+$response->isSuccessful(); // вернёт true, если ошибок нет
+```
+
+---
+
+[Вернуться к содержанию](readme.md#toc)
diff --git a/docs/vats.md b/docs/vats.md
deleted file mode 100644
index 456dca4..0000000
--- a/docs/vats.md
+++ /dev/null
@@ -1,194 +0,0 @@
-# Работа со ставками НДС
-
-[Вернуться к содержанию](readme.md)
-
----
-
-## Один объект
-
-Объект ставки НДС инициализируется следующим образом:
-
-```php
-$vat = new AtolOnline\Entities\Vat();
-```
-
-У объекта ставки должны быть указаны все следующие атрибуты:
-* тип ставки (теги ФФД: 1199 для предмета расчёта; 1105, 1104, 1103, 1102, 1107, 1106 для чека) - все типы перечислены в классе `AtolOnline\Constants\VatTypes` (по умолчанию `NONE`)
-* размер налога (теги ФФД: 1200 для предмета расчёта; 1105, 1104, 1103, 1102, 1107, 1106 для чека)
-
-> Все эти атрибуты являются **обязательными**.
-
-Установить тип ставки НДС можно следующими способами:
-
-```php
-// 1 способ - через конструктор
-$vat = new AtolOnline\Entities\Vat(
- AtolOnline\Constants\VatTypes::VAT20 // тип ставки
-);
-
-// 2 способ - через сеттер
-$vat = (new AtolOnline\Entities\Vat())
- ->setType(AtolOnline\Constants\VatTypes::VAT20); // тип ставки
-```
-
-Размер налога высчитывается автоматически из общей суммы.
-Сумму, от которой нужно расчитать размер налога, можно передать следующими способами:
-
-```php
-// 1 способ - через конструктор
-$vat = new AtolOnline\Entities\Vat(
- AtolOnline\Constants\VatTypes::VAT10, // тип ставки
- 1234.56 // общая сумма в рублях
-);
-
-// 2 способ - через сеттер
-$vat = (new AtolOnline\Entities\Vat())
- ->setType(AtolOnline\Constants\VatTypes::VAT10) // тип ставки
- ->setSum(150); // общая сумма в рублях
-```
-
-Сумму можно установить и до установки типа ставки.
-Объект её запомнит и пересчитает итоговый размер налога при смене типа ставки:
-
-```php
-$vat = (new AtolOnline\Entities\Vat())
- ->setSum(150) // общая сумма в рублях
- ->setType(AtolOnline\Constants\VatTypes::VAT10); // тип ставки
-```
-
-Получить установленную расчётную сумму в рублях можно через геттер `getSum()`:
-
-```php
-var_dump($vat->getSum());
-// double(150)
-```
-
-Получить расчитанный размер налога в рублях можно через геттер `getFinalSum()`:
-
-```php
-var_dump($vat->getFinalSum());
-// double(15): для примера выше это 10% от 150р = 15р
-```
-
-Общую сумму, из которой расчитывается размер налога, можно увеличить, используя метод `addSum()`.
-Указанная в рублях сумма увеличится и итоговый размер налога пересчитается.
-Для уменьшения суммы следует передать отрицательное число.
-
-Разберём комплексный пример изменения типа ставки и расчётной суммы:
-
-```php
-use AtolOnline\{Entities\Vat, Constants\VatTypes};
-
-$vat = new Vat(VatTypes::VAT20, 120);
-echo "НДС20 от 120р: ";
-var_dump($vat->getFinalSum());
-
-echo "НДС10 от 120р: ";
-$vat->setType(VatTypes::VAT10);
-var_dump($vat->getFinalSum());
-
-$vat->addSum(40);
-echo "НДС10 от {$vat->getSum()}р: ";
-var_dump($vat->getFinalSum());
-
-$vat->setType(VatTypes::VAT20)->addSum(-20);
-echo "НДС20 от {$vat->getSum()}р: ";
-var_dump($vat->getFinalSum());
-
-$vat->setType(VatTypes::VAT120);
-echo "НДС20/120 от {$vat->getSum()}р: ";
-var_dump($vat->getFinalSum());
-```
-
-Результат будет следующим:
-
-```
-НДС20 от 120р:
-double(24)
-НДС10 от 120р:
-double(12)
-НДС10 от 160р:
-double(16)
-НДС20 от 140р:
-double(28)
-НДС20/120 от 140р:
-double(23.33)
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $vat;
-$json_string = (string)$vat;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $vat->jsonSerialize();
-```
-
-
-## Массив объектов ставок НДС
-
-> Максимальное количество в массиве - 6.
-
-Массив инициализируется следующим образом:
-
-```php
-$vat_array = new AtolOnline\Entities\VatArray();
-```
-
-Чтобы задать содержимое массива, используйте метод `set()`:
-
-```php
-use AtolOnline\{Constants\VatTypes, Entities\Vat};
-
-$vat_array->set([
- new Vat(VatTypes::VAT10, 123),
- new Vat(VatTypes::VAT110, 53.2),
- new Vat(VatTypes::VAT20, 23.99),
- new Vat(VatTypes::VAT120, 11.43)
-]);
-```
-
-Очистить его можно передачей в сеттер пустого массива:
-
-```php
-$vat_array->set([]);
-```
-
-Чтобы добавить объект к существующим элементам массива, используйте метод `add()`:
-
-```php
-use AtolOnline\{Constants\VatTypes, Entities\Vat};
-
-$vat = new Vat(VatTypes::VAT20, 20);
-$vat_array->add($vat);
-```
-
-Методы `set()` и `add()` проверяют количество элементов в массиве перед его обновлением.
-Выбрасывают исключение `AtolTooManyVatsException` (если в массиве уже максимальное количество объектов).
-
-Чтобы получить содержимое массива, используйте метод `get()`:
-
-```php
-$vat_array->get();
-```
-
-Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
-
-```php
-echo $vat_array;
-$json_string = (string)$vat_array;
-```
-
-Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
-
-```php
-$json_array = $vat_array->jsonSerialize();
-```
-
----
-
-[Вернуться к содержанию](readme.md)
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 8814837..c851dd7 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,25 +1,34 @@
+
+
+ colors="true">
-
- ClientTest.php
- CompanyTest.php
- VatTest.php
- ./tests/Unit
+
+ tests/AtolOnline/Tests/HelpersTest.php
-
- ItemTest.php
- ./tests/Feature
+
+ tests/AtolOnline/Tests/Entities
+
+
+ tests/AtolOnline/Tests/Collections
+
+
+ tests/AtolOnline/Tests/Api
-
\ No newline at end of file
+
+
+
+ src
+
+
+
diff --git a/src/Api/AtolClient.php b/src/Api/AtolClient.php
new file mode 100644
index 0000000..08caa92
--- /dev/null
+++ b/src/Api/AtolClient.php
@@ -0,0 +1,331 @@
+http = new Client(
+ array_merge($config, [
+ 'http_errors' => $config['http_errors'] ?? false,
+ ])
+ );
+ $this->setTestMode($test_mode);
+ !is_null($login) && $this->setLogin($login);
+ !is_null($password) && $this->setPassword($password);
+ }
+
+ /**
+ * Возвращает последний запрос к серверу
+ *
+ * @return array
+ */
+ public function getLastRequest(): array
+ {
+ return $this->request;
+ }
+
+ /**
+ * Возвращает последний ответ сервера
+ *
+ * @return AtolResponse|null
+ */
+ public function getLastResponse(): ?AtolResponse
+ {
+ return $this->response;
+ }
+
+ /**
+ * Возвращает установленный флаг тестового режима
+ *
+ * @return bool
+ */
+ public function isTestMode(): bool
+ {
+ return $this->test_mode;
+ }
+
+ /**
+ * Устанавливает флаг тестового режима
+ *
+ * @param bool $test_mode
+ * @return $this
+ */
+ public function setTestMode(bool $test_mode = true): self
+ {
+ $this->test_mode = $test_mode;
+ return $this;
+ }
+
+ /**
+ * Возвращает текущий токен авторизации
+ *
+ * @return string|null
+ */
+ public function getToken(): ?string
+ {
+ return $this->token;
+ }
+
+ /**
+ * Устанавливает токен авторизации
+ *
+ * @param string|null $token
+ * @return $this
+ */
+ public function setToken(?string $token): self
+ {
+ $this->token = $token;
+ return $this;
+ }
+
+ /**
+ * Возвращает логин доступа к API
+ *
+ * @return string|null
+ */
+ #[Pure]
+ public function getLogin(): ?string
+ {
+ return $this->isTestMode()
+ ? TestEnvParams::FFD105()['login']
+ : $this->login;
+ }
+
+ /**
+ * Устанавливает логин доступа к API
+ *
+ * @param string $login
+ * @return $this
+ * @throws EmptyLoginException
+ * @throws TooLongLoginException
+ */
+ public function setLogin(string $login): self
+ {
+ $login = trim($login);
+ if (empty($login)) {
+ throw new EmptyLoginException();
+ } elseif (mb_strlen($login) > Constraints::MAX_LENGTH_LOGIN) {
+ throw new TooLongLoginException($login);
+ }
+ $this->login = $login;
+ return $this;
+ }
+
+ /**
+ * Возвращает пароль доступа к API
+ *
+ * @return string|null
+ */
+ #[Pure]
+ public function getPassword(): ?string
+ {
+ return $this->isTestMode()
+ ? TestEnvParams::FFD105()['password']
+ : $this->password;
+ }
+
+ /**
+ * Устанавливает пароль доступа к API
+ *
+ * @param string $password
+ * @return $this
+ * @throws EmptyPasswordException Пароль ККТ не может быть пустым
+ * @throws TooLongPasswordException Слишком длинный пароль ККТ
+ */
+ public function setPassword(string $password): self
+ {
+ if (empty($password)) {
+ throw new EmptyPasswordException();
+ } elseif (mb_strlen($password) > Constraints::MAX_LENGTH_PASSWORD) {
+ throw new TooLongPasswordException($password);
+ }
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * Возвращает набор заголовков для HTTP-запроса
+ *
+ * @return array
+ */
+ #[Pure]
+ private function getHeaders(): array
+ {
+ $headers['Content-type'] = 'application/json; charset=utf-8';
+ if ($this->getToken()) {
+ $headers['Token'] = $this->getToken();
+ }
+ return $headers;
+ }
+
+ /**
+ * Возвращает полный URL для запроса
+ *
+ * @param string $method
+ * @return string
+ */
+ protected function getUrlToMethod(string $method): string
+ {
+ return $this->getMainEndpoint() . '/' . trim($method);
+ }
+
+ /**
+ * Отправляет авторизационный запрос на сервер АТОЛ и возвращает авторизационный токен
+ *
+ * @return string|null
+ * @throws AuthFailedException
+ * @throws EmptyPasswordException
+ * @throws EmptyLoginException
+ * @throws GuzzleException
+ */
+ protected function doAuth(): ?string
+ {
+ $result = $this->sendRequest('POST', $this->getAuthEndpoint(), [
+ 'login' => $this->getLogin() ?? throw new EmptyLoginException(),
+ 'pass' => $this->getPassword() ?? throw new EmptyPasswordException(),
+ ]);
+ if (!$result->isSuccessful() || !$result->getContent()->token) {
+ throw new AuthFailedException($result);
+ }
+ return $result->getContent()?->token;
+ }
+
+ /**
+ * Отправляет запрос и возвращает декодированный ответ
+ *
+ * @param string $http_method Метод HTTP
+ * @param string $url URL
+ * @param array|null $data Данные для передачи
+ * @param array|null $options Параметры Guzzle
+ * @return AtolResponse
+ * @throws GuzzleException
+ * @see https://guzzle.readthedocs.io/en/latest/request-options.html
+ */
+ protected function sendRequest(
+ string $http_method,
+ string $url,
+ ?array $data = null,
+ ?array $options = null
+ ): AtolResponse {
+ $http_method = strtoupper(trim($http_method));
+ $options['headers'] = array_merge($this->getHeaders(), $options['headers'] ?? []);
+ $http_method != 'GET' && $options['json'] = $data;
+ $this->request = array_merge([
+ 'method' => $http_method,
+ 'url' => $url,
+ ], $options);
+ $response = $this->http->request($http_method, $url, $options);
+ return $this->response = new AtolResponse($response);
+ }
+
+ /**
+ * Выполняет авторизацию на сервере АТОЛ
+ * Авторизация выполнится только если неизвестен токен
+ *
+ * @return bool
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ */
+ public function auth(): bool
+ {
+ if (empty($this->getToken()) && $token = $this->doAuth()) {
+ $this->setToken($token);
+ }
+ return !empty($this->getToken());
+ }
+
+ /**
+ * Возвращает URL для запроса авторизации
+ *
+ * @return string
+ */
+ abstract protected function getAuthEndpoint(): string;
+
+ /**
+ * Возвращает URL для запросов
+ *
+ * @return string
+ */
+ abstract protected function getMainEndpoint(): string;
+}
diff --git a/src/AtolOnline/Api/KktResponse.php b/src/Api/AtolResponse.php
similarity index 66%
rename from src/AtolOnline/Api/KktResponse.php
rename to src/Api/AtolResponse.php
index c52bb9b..5d7739d 100644
--- a/src/AtolOnline/Api/KktResponse.php
+++ b/src/Api/AtolResponse.php
@@ -1,52 +1,60 @@
code = $response->getStatusCode();
$this->headers = $response->getHeaders();
- $this->content = json_decode($response->getBody());
+ $this->content = json_decode((string)$response->getBody());
}
-
+
/**
* Возвращает заголовки ответа
*
@@ -56,18 +64,19 @@ class KktResponse implements JsonSerializable
{
return $this->headers;
}
-
+
/**
* Возвращает запрошенный параметр из декодированного объекта результата
*
* @param $name
* @return mixed
*/
- public function __get($name)
+ #[Pure]
+ public function __get($name): mixed
{
- return $this->getContent()->$name;
+ return $this->getContent()?->$name;
}
-
+
/**
* Возвращает код ответа
*
@@ -77,42 +86,49 @@ class KktResponse implements JsonSerializable
{
return $this->code;
}
-
+
/**
* Возвращает объект результата запроса
*
- * @return stdClass|null
+ * @return mixed
*/
- public function getContent(): ?stdClass
+ public function getContent(): mixed
{
return $this->content;
}
-
+
/**
* Проверяет успешность запроса по соержимому результата
*
* @return bool
*/
- public function isValid()
+ #[Pure]
+ public function isSuccessful(): bool
{
return !empty($this->getCode())
&& !empty($this->getContent())
&& empty($this->getContent()->error)
- && (int)$this->getCode() < 400;
+ && $this->getCode() < 400;
}
-
+
/**
* Возвращает текстовое представление
*/
- public function __toString()
+ public function __toString(): string
{
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
}
-
+
/**
* @inheritDoc
*/
- public function jsonSerialize()
+ #[ArrayShape([
+ 'code' => 'int',
+ 'headers' => 'array|\string[][]',
+ 'body' => 'mixed',
+ ]
+ )]
+ public function jsonSerialize(): array
{
return [
'code' => $this->code,
@@ -120,4 +136,4 @@ class KktResponse implements JsonSerializable
'body' => $this->content,
];
}
-}
\ No newline at end of file
+}
diff --git a/src/Api/Fiscalizer.php b/src/Api/Fiscalizer.php
new file mode 100644
index 0000000..17c90b5
--- /dev/null
+++ b/src/Api/Fiscalizer.php
@@ -0,0 +1,378 @@
+setGroup($group);
+ }
+
+ /**
+ * Возвращает группу доступа к ККТ в соответствии с флагом тестового режима
+ *
+ * @return string|null
+ */
+ #[Pure]
+ public function getGroup(): ?string
+ {
+ return $this->isTestMode()
+ ? TestEnvParams::FFD105()['group']
+ : $this->group;
+ }
+
+ /**
+ * Устанавливает группу доступа к ККТ
+ *
+ * @param string $group
+ * @return $this
+ * @throws EmptyGroupException
+ */
+ public function setGroup(string $group): self
+ {
+ // критерии к длине строки не описаны ни в схеме, ни в документации
+ empty($group = trim($group)) && throw new EmptyGroupException();
+ $this->group = $group;
+ return $this;
+ }
+
+ /**
+ * Возвращает URL для приёма колбеков
+ *
+ * @return string|null
+ */
+ public function getCallbackUrl(): ?string
+ {
+ return $this->callback_url;
+ }
+
+ /**
+ * Устанавливает URL для приёма колбеков
+ *
+ * @param string|null $url
+ * @return $this
+ * @throws TooLongCallbackUrlException
+ * @throws InvalidCallbackUrlException
+ */
+ public function setCallbackUrl(?string $url = null): self
+ {
+ $url = trim((string)$url);
+ if (mb_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
+ throw new TooLongCallbackUrlException($url);
+ } elseif (!empty($url) && !preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
+ throw new InvalidCallbackUrlException();
+ }
+ $this->callback_url = $url ?: null;
+ return $this;
+ }
+
+ /**
+ * Регистрирует документ прихода
+ *
+ * @param Receipt $receipt Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sell(Receipt $receipt, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('sell', $receipt, $external_id);
+ }
+
+ /**
+ * Регистрирует документ возврата прихода
+ *
+ * @param Receipt $receipt Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sellRefund(Receipt $receipt, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('sell_refund', $receipt, $external_id);
+ }
+
+ /**
+ * Регистрирует документ коррекции прихода
+ *
+ * @param Correction $correction Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sellCorrect(Correction $correction, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('sell_correction', $correction, $external_id);
+ }
+
+ /**
+ * Регистрирует документ расхода
+ *
+ * @param Receipt $receipt Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buy(Receipt $receipt, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('buy', $receipt, $external_id);
+ }
+
+ /**
+ * Регистрирует документ возврата расхода
+ *
+ * @param Receipt $receipt Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buyRefund(Receipt $receipt, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('buy_refund', $receipt, $external_id);
+ }
+
+ /**
+ * Регистрирует документ коррекции расхода
+ *
+ * @param Correction $correction Объект документа
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buyCorrect(Correction $correction, ?string $external_id = null): ?AtolResponse
+ {
+ return $this->registerDocument('buy_correction', $correction, $external_id);
+ }
+
+ /**
+ * Проверяет статус чека на ККТ один раз
+ *
+ * @param string $uuid UUID регистрации
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidUuidException
+ */
+ public function getDocumentStatus(string $uuid): ?AtolResponse
+ {
+ !Uuid::isValid($uuid = trim($uuid)) && throw new InvalidUuidException($uuid);
+ return $this->auth()
+ ? $this->sendRequest('GET', $this->getFullUrl('report/' . $uuid))
+ : null;
+ }
+
+ /**
+ * Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
+ * Вернёт результат как только при очередной проверке сменится статус регистрации документа.
+ *
+ * @param string $uuid UUID регистрации
+ * @param int $retry_count Количество попыток
+ * @param int $timeout Таймаут в секундах между попытками
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidUuidException
+ */
+ public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1): ?AtolResponse
+ {
+ $try = 0;
+ do {
+ $response = $this->getDocumentStatus($uuid);
+ if ($response->isSuccessful() && $response->getContent()->status == 'done') {
+ break;
+ } else {
+ sleep($timeout);
+ }
+ ++$try;
+ } while ($try < $retry_count);
+ return $response;
+ }
+
+ /**
+ * Отправляет документ на регистрацию
+ *
+ * @param string $api_method Метод API
+ * @param Receipt|Correction $document Документ
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ protected function registerDocument(
+ string $api_method,
+ Receipt|Correction $document,
+ ?string $external_id = null
+ ): ?AtolResponse {
+ $this->isTestMode() && $document->getCompany()
+ ->setInn(TestEnvParams::FFD105()['inn'])
+ ->setPaymentAddress(TestEnvParams::FFD105()['payment_address']);
+ $this->isTestMode() && $document instanceof Receipt
+ && $document->getClient()->setInn(TestEnvParams::FFD105()['inn']);
+ $this->getCallbackUrl() && $data['service'] = ['callback_url' => $this->getCallbackUrl()];
+ return $this->auth()
+ ? $this->sendRequest(
+ 'POST',
+ $this->getFullUrl($api_method),
+ array_merge($data ?? [], [
+ 'timestamp' => date('d.m.Y H:i:s'),
+ 'external_id' => $external_id ?: Uuid::uuid4()->toString(),
+ $document::DOC_TYPE => $document->jsonSerialize(),
+ ])
+ )
+ : null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ protected function getAuthEndpoint(): string
+ {
+ return $this->isTestMode()
+ ? 'https://testonline.atol.ru/possystem/v4/getToken'
+ : 'https://online.atol.ru/possystem/v4/getToken';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ protected function getMainEndpoint(): string
+ {
+ return $this->isTestMode()
+ ? 'https://testonline.atol.ru/possystem/v4/'
+ : 'https://online.atol.ru/possystem/v4/';
+ }
+
+ /**
+ * Возвращает полный URL метода API
+ *
+ * @param string $api_method
+ * @return string
+ */
+ #[Pure]
+ protected function getFullUrl(string $api_method): string
+ {
+ return $this->getMainEndpoint() . $this->getGroup() . '/' . trim($api_method);
+ }
+}
diff --git a/src/Api/Monitor.php b/src/Api/Monitor.php
new file mode 100644
index 0000000..77ecaf8
--- /dev/null
+++ b/src/Api/Monitor.php
@@ -0,0 +1,129 @@
+isTestMode()
+ ? 'https://testonline.atol.ru/api/auth/v1/gettoken'
+ : 'https://online.atol.ru/api/auth/v1/gettoken';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ protected function getMainEndpoint(): string
+ {
+ return $this->isTestMode()
+ ? 'https://testonline.atol.ru/api/kkt/v1'
+ : 'https://online.atol.ru/api/kkt/v1';
+ }
+
+ /**
+ * Получает от API информацию обо всех ККТ и ФН в рамках группы
+ *
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return AtolResponse|null
+ * @throws GuzzleException
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
+ */
+ protected function fetchAll(?int $limit = null, ?int $offset = null): ?AtolResponse
+ {
+ $params = [];
+ !is_null($limit) && $params['limit'] = $limit;
+ !is_null($offset) && $params['offset'] = $offset;
+ return $this->auth()
+ ? $this->sendRequest('GET', self::getUrlToMethod('cash-registers'), $params)
+ : null;
+ }
+
+ /**
+ * Возвращает информацию обо всех ККТ и ФН в рамках группы
+ *
+ * @param int|null $limit
+ * @param int|null $offset
+ * @return Collection
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
+ */
+ public function getAll(?int $limit = null, ?int $offset = null): Collection
+ {
+ $collection = collect($this->fetchAll($limit, $offset)->getContent());
+ return $collection->map(fn($data) => new Kkt($data));
+ }
+
+ /**
+ * Получает от API информацию о конкретной ККТ по её серийному номеру
+ *
+ * @param string $serial_number
+ * @return AtolResponse
+ * @throws GuzzleException
+ * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
+ */
+ protected function fetchOne(string $serial_number): AtolResponse
+ {
+ return $this->sendRequest(
+ 'GET',
+ self::getUrlToMethod('cash-registers') . '/' . trim($serial_number),
+ options: [
+ 'headers' => [
+ 'Accept' => 'application/hal+json',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Возвращает информацию о конкретной ККТ по её серийному номеру
+ *
+ * @param string $serial_number
+ * @return Kkt
+ * @throws GuzzleException
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
+ */
+ public function getOne(string $serial_number): Kkt
+ {
+ return new Kkt($this->fetchOne($serial_number)->getContent()->data);
+ }
+}
diff --git a/src/AtolOnline/Api/Kkt.php b/src/AtolOnline/Api/Kkt.php
deleted file mode 100644
index 54c2d45..0000000
--- a/src/AtolOnline/Api/Kkt.php
+++ /dev/null
@@ -1,576 +0,0 @@
-resetKktConfig();
- if ($group) {
- $this->setGroup($group);
- }
- if ($login) {
- $this->setLogin($login);
- }
- if ($login) {
- $this->setPassword($pass);
- }
- $this->setTestMode($test_mode);
- $guzzle_config['base_uri'] = $this->getEndpoint();
- $guzzle_config['http_errors'] = $guzzle_config['http_errors'] ?? false;
- parent::__construct($guzzle_config);
- }
-
- /**
- * Устанавливает группу доступа к ККТ
- *
- * @param string $group
- * @return $this
- */
- public function setGroup(string $group)
- {
- $this->kkt_config['prod']['group'] = $group;
- return $this;
- }
-
- /**
- * Возвращает группу доступа к ККТ в соответствии с флагом тестового режима
- *
- * @return string
- */
- public function getGroup(): string
- {
- return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['group'];
- }
-
- /**
- * Устанавливает логин доступа к ККТ
- *
- * @param string $login
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolKktLoginEmptyException Логин ККТ не может быть пустым
- * @throws \AtolOnline\Exceptions\AtolKktLoginTooLongException Слишком длинный логин ККТ
- */
- public function setLogin(string $login)
- {
- if (empty($login)) {
- throw new AtolKktLoginEmptyException();
- } elseif (valid_strlen($login) > Constraints::MAX_LENGTH_LOGIN) {
- throw new AtolKktLoginTooLongException($login, Constraints::MAX_LENGTH_LOGIN);
- }
- $this->kkt_config['prod']['login'] = $login;
- return $this;
- }
-
- /**
- * Возвращает логин доступа к ККТ в соответствии с флагом тестового режима
- *
- * @return string
- */
- public function getLogin(): string
- {
- return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['login'];
- }
-
- /**
- * Устанавливает пароль доступа к ККТ
- *
- * @param string $password
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolKktPasswordEmptyException Пароль ККТ не может быть пустым
- * @throws \AtolOnline\Exceptions\AtolKktPasswordTooLongException Слишком длинный пароль ККТ
- */
- public function setPassword(string $password)
- {
- if (empty($password)) {
- throw new AtolKktPasswordEmptyException();
- } elseif (valid_strlen($password) > Constraints::MAX_LENGTH_PASSWORD) {
- throw new AtolKktPasswordTooLongException($password, Constraints::MAX_LENGTH_PASSWORD);
- }
- $this->kkt_config['prod']['pass'] = $password;
- return $this;
- }
-
- /**
- * Возвращает логин ККТ в соответствии с флагом тестового режима
- *
- * @return string
- */
- public function getPassword(): string
- {
- return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['pass'];
- }
-
- /**
- * Устанавливает URL для приёма колбеков
- *
- * @param string $url
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolCallbackUrlTooLongException Слишком длинный Callback URL
- * @throws \AtolOnline\Exceptions\AtolInvalidCallbackUrlException Невалидный Callback URL
- */
- public function setCallbackUrl(string $url)
- {
- if (valid_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
- throw new AtolCallbackUrlTooLongException($url, Constraints::MAX_LENGTH_CALLBACK_URL);
- } elseif (!preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
- throw new AtolInvalidCallbackUrlException('Callback URL not matches with pattern');
- }
- $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'] = $url;
- return $this;
- }
-
- /**
- * Возвращает URL для приёма колбеков
- *
- * @return string
- */
- public function getCallbackUrl(): string
- {
- return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['callback_url'];
- }
-
- /**
- * Возвращает последний ответ сервера
- *
- * @return mixed
- */
- public function getLastResponse()
- {
- return $this->last_response;
- }
-
- /**
- * Возвращает флаг тестового режима
- *
- * @return bool
- */
- public function isTestMode(): bool
- {
- return $this->is_test_mode;
- }
-
- /**
- * Устанавливает флаг тестового режима
- *
- * @param bool $test_mode
- * @return $this
- */
- public function setTestMode(bool $test_mode = true)
- {
- $this->is_test_mode = $test_mode;
- return $this;
- }
-
- /**
- * Регистрирует документ прихода
- *
- * @param \AtolOnline\Entities\Document $document Объект документа
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function sell(Document $document, ?string $external_id = null)
- {
- if ($document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException('Некорректная операция над документом коррекции');
- }
- return $this->registerDocument('sell', 'receipt', $document, $external_id);
- }
-
- /**
- * Регистрирует документ возврата прихода
- *
- * @param \AtolOnline\Entities\Document $document Объект документа
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
- * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function sellRefund(Document $document, ?string $external_id = null)
- {
- if ($document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException('Invalid operation on correction document');
- }
- return $this->registerDocument('sell_refund', 'receipt', $document->clearVats(), $external_id);
- }
-
- /**
- * Регистрирует документ коррекции прихода
- *
- * @param \AtolOnline\Entities\Document $document Объект документа
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function sellCorrection(Document $document, ?string $external_id = null)
- {
- if (!$document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException();
- }
- $document->setClient(null)->setItems([]);
- return $this->registerDocument('sell_correction', 'correction', $document, $external_id);
- }
-
- /**
- * Регистрирует документ расхода
- *
- * @param \AtolOnline\Entities\Document $document
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function buy(Document $document, ?string $external_id = null)
- {
- if ($document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException('Invalid operation on correction document');
- }
- return $this->registerDocument('buy', 'receipt', $document, $external_id);
- }
-
- /**
- * Регистрирует документ возврата расхода
- *
- * @param \AtolOnline\Entities\Document $document
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе есть данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
- * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function buyRefund(Document $document, ?string $external_id = null)
- {
- if ($document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException('Invalid operation on correction document');
- }
- return $this->registerDocument('buy_refund', 'receipt', $document->clearVats(), $external_id);
- }
-
- /**
- * Регистрирует документ коррекции расхода
- *
- * @param \AtolOnline\Entities\Document $document
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolCorrectionInfoException В документе отсутствуют данные коррекции
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длтина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function buyCorrection(Document $document, ?string $external_id = null)
- {
- if (!$document->getCorrectionInfo()) {
- throw new AtolCorrectionInfoException();
- }
- $document->setClient(null)->setItems([]);
- return $this->registerDocument('buy_correction', 'correction', $document, $external_id);
- }
-
- /**
- * Проверяет статус чека на ККТ один раз
- *
- * @param string $uuid UUID регистрации
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolInvalidUuidException Некорректный UUID документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function getDocumentStatus(string $uuid)
- {
- $uuid = trim($uuid);
- if (!Uuid::isValid($uuid)) {
- throw new AtolInvalidUuidException($uuid);
- }
- $this->auth();
- return $this->sendAtolRequest('GET', 'report/'.$uuid);
- }
-
- /**
- * Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
- * Вернёт результат как только при очередной проверке сменится статус регистрации документа.
- *
- * @param string $uuid UUID регистрации
- * @param int $retry_count Количество попыток
- * @param int $timeout Таймаут в секундах между попытками
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolInvalidUuidException Некорректный UUID документа
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1)
- {
- $try = 0;
- do {
- $response = $this->getDocumentStatus($uuid);
- if ($response->isValid() && $response->getContent()->status == 'done') {
- break;
- } else {
- sleep($timeout);
- }
- ++$try;
- } while ($try < $retry_count);
- return $response;
- }
-
- /**
- * Возвращает текущий токен авторизации
- *
- * @return string
- */
- public function getAuthToken(): ?string
- {
- return $this->auth_token;
- }
-
- /**
- * Устанавливает заранее известный токен авторизации
- *
- * @param string|null $auth_token
- * @return $this
- */
- public function setAuthToken(?string $auth_token)
- {
- $this->auth_token = $auth_token;
- return $this;
- }
-
- /**
- * Сбрасывает настройки ККТ по умолчанию
- */
- protected function resetKktConfig(): void
- {
- $this->kkt_config['prod']['group'] = '';
- $this->kkt_config['prod']['login'] = '';
- $this->kkt_config['prod']['pass'] = '';
- $this->kkt_config['prod']['url'] = 'https://online.atol.ru/possystem/v4';
- $this->kkt_config['prod']['callback_url'] = '';
- $this->kkt_config['test']['group'] = TestEnvParams::GROUP;
- $this->kkt_config['test']['login'] = TestEnvParams::LOGIN;
- $this->kkt_config['test']['pass'] = TestEnvParams::PASSWORD;
- $this->kkt_config['test']['url'] = 'https://testonline.atol.ru/possystem/v4';
- $this->kkt_config['test']['callback_url'] = '';
- }
-
- /**
- * Возвращает набор заголовков для HTTP-запроса
- *
- * @return array
- */
- protected function getHeaders()
- {
- $headers['Content-type'] = 'application/json; charset=utf-8';
- if ($this->getAuthToken()) {
- $headers['Token'] = $this->getAuthToken();
- }
- return $headers;
- }
-
- /**
- * Возвращает адрес сервера в соответствии с флагом тестового режима
- *
- * @return string
- */
- protected function getEndpoint(): string
- {
- return $this->kkt_config[$this->isTestMode() ? 'test' : 'prod']['url'];
- }
-
- /**
- * Возвращает полный URL до метода API
- *
- * @param string $to_method
- * @param array|null $get_parameters
- * @return string
- */
- protected function makeUrl(string $to_method, array $get_parameters = null)
- {
- $url = $this->getEndpoint().($this->getAuthToken() ? '/'.$this->getGroup() : '').'/'.$to_method;
- if ($get_parameters && is_array($get_parameters)) {
- $url .= '?'.http_build_query($get_parameters);
- }
- return $url;
- }
-
- /**
- * Делает запрос, возвращает декодированный ответ
- *
- * @param string $http_method Метод HTTP (GET, POST и пр)
- * @param string $api_method Метод API
- * @param mixed $data Данные для передачи
- * @param array|null $options Параметры Guzzle
- * @return \AtolOnline\Api\KktResponse
- * @throws \GuzzleHttp\Exception\GuzzleException
- * @see https://guzzle.readthedocs.io/en/latest/request-options.html
- */
- protected function sendAtolRequest(string $http_method, string $api_method, $data = null, array $options = null)
- {
- $http_method = strtoupper($http_method);
- $options['headers'] = $this->getHeaders();
- $url = $http_method == 'GET'
- ? $this->makeUrl($api_method, $data)
- : $this->makeUrl($api_method, ['token' => $this->getAuthToken()]);
- if ($http_method != 'GET') {
- $options['json'] = $data;
- }
- $response = $this->request($http_method, $url, $options);
- return $this->last_response = new KktResponse($response);
- }
-
- /**
- * Производит авторизацию на ККТ и получает токен доступа для дальнейших HTTP-запросов
- *
- * @return bool
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- protected function auth()
- {
- if (!$this->getAuthToken()) {
- $result = $this->sendAtolRequest('GET', 'getToken', [
- 'login' => $this->getLogin(),
- 'pass' => $this->getPassword(),
- ]);
- if (!$result->isValid() || !$result->getContent()->token) {
- throw new AtolAuthFailedException($result);
- }
- $this->auth_token = $result->getContent()->token;
- }
- return true;
- }
-
- /**
- * Отправляет документ на регистрацию
- *
- * @param string $api_method Метод API
- * @param string $type Тип документа: receipt, correction
- * @param \AtolOnline\Entities\Document $document Объект документа
- * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
- * @return \AtolOnline\Api\KktResponse
- * @throws \AtolOnline\Exceptions\AtolAuthFailedException Ошибка авторизации
- * @throws \AtolOnline\Exceptions\AtolWrongDocumentTypeException Некорректный тип документа
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException Некорректная длина ИНН
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- * @throws \GuzzleHttp\Exception\GuzzleException
- */
- protected function registerDocument(string $api_method, string $type, Document $document, ?string $external_id = null)
- {
- $type = trim($type);
- if (!in_array($type, ['receipt', 'correction'])) {
- throw new AtolWrongDocumentTypeException($type);
- }
- $this->auth();
- if ($this->isTestMode()) {
- $document->setCompany(($document->getCompany() ?: new Company())
- ->setInn(TestEnvParams::INN)
- ->setSno(TestEnvParams::SNO)
- ->setPaymentAddress(TestEnvParams::PAYMENT_ADDRESS));
- }
- $data['timestamp'] = date('d.m.y H:i:s');
- $data['external_id'] = $external_id ?: Uuid::uuid4()->toString();
- $data[$type] = $document;
- if ($this->getCallbackUrl()) {
- $data['service'] = ['callback_url' => $this->getCallbackUrl()];
- }
- return $this->sendAtolRequest('POST', trim($api_method), $data);
- }
-}
diff --git a/src/AtolOnline/Constants/Constraints.php b/src/AtolOnline/Constants/Constraints.php
deleted file mode 100644
index 92c1040..0000000
--- a/src/AtolOnline/Constants/Constraints.php
+++ /dev/null
@@ -1,76 +0,0 @@
-setName($name);
- }
- if ($email) {
- $this->setEmail($email);
- }
- if ($phone) {
- $this->setPhone($phone);
- }
- if ($inn) {
- $this->setInn($inn);
- }
- }
-
- /**
- * Возвращает имя покупателя. Тег ФФД - 1227.
- *
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * Устанавливает имя покупателя
- * Тег ФФД - 1227.
- *
- * @param string $name
- * @return $this
- * @throws AtolNameTooLongException
- */
- public function setName(string $name)
- {
- $name = trim($name);
- if (valid_strlen($name) > Constraints::MAX_LENGTH_CLIENT_NAME) {
- throw new AtolNameTooLongException($name, Constraints::MAX_LENGTH_CLIENT_NAME);
- }
- $this->name = $name;
- return $this;
- }
-
- /**
- * Возвращает телефон покупателя.
- * Тег ФФД - 1008.
- *
- * @return string
- */
- public function getPhone()
- {
- return $this->phone ?? '';
- }
-
- /**
- * Устанавливает телефон покупателя.
- * Тег ФФД - 1008.
- * Входная строка лишается всех знаков, кроме цифр и знака '+'.
- *
- * @param string $phone
- * @return $this
- * @throws AtolPhoneTooLongException
- */
- public function setPhone(string $phone)
- {
- $phone = preg_replace("/[^0-9+]/", '', $phone);
- if (valid_strlen($phone) > Constraints::MAX_LENGTH_CLIENT_PHONE) {
- throw new AtolPhoneTooLongException($phone, Constraints::MAX_LENGTH_CLIENT_PHONE);
- }
- $this->phone = $phone;
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- $json = [];
- if ($this->getName()) {
- $json['name'] = $this->getName() ?? '';
- }
- if ($this->getEmail()) {
- $json['email'] = $this->getEmail() ?? '';
- }
- if ($this->getPhone()) {
- $json['phone'] = $this->getPhone() ?? '';
- }
- if ($this->getInn()) {
- $json['inn'] = $this->getInn() ?? '';
- }
- return $json;
- }
-}
diff --git a/src/AtolOnline/Entities/Company.php b/src/AtolOnline/Entities/Company.php
deleted file mode 100644
index 97b3e69..0000000
--- a/src/AtolOnline/Entities/Company.php
+++ /dev/null
@@ -1,138 +0,0 @@
-setSno($sno);
- }
- if ($inn) {
- $this->setInn($inn);
- }
- if ($paymentAddress) {
- $this->setPaymentAddress($paymentAddress);
- }
- if ($email) {
- $this->setEmail($email);
- }
- }
-
- /**
- * Возвращает установленный тип налогообложения. Тег ФФД - 1055.
- *
- * @return string
- */
- public function getSno()
- {
- return $this->sno;
- }
-
- /**
- * Устанавливает тип налогообложения. Тег ФФД - 1055.
- *
- * @param string $sno
- * @return $this
- */
- public function setSno(string $sno)
- {
- $this->sno = trim($sno);
- return $this;
- }
-
- /**
- * Возвращает установленный адрес места расчётов. Тег ФФД - 1187.
- *
- * @return string
- */
- public function getPaymentAddress()
- {
- return $this->payment_address;
- }
-
- /**
- * Устанавливает адрес места расчётов. Тег ФФД - 1187.
- *
- * @param string $payment_address
- * @return $this
- * @throws AtolPaymentAddressTooLongException Слишком длинный адрес места расчётов
- */
- public function setPaymentAddress(string $payment_address)
- {
- $payment_address = trim($payment_address);
- if (valid_strlen($payment_address) > Constraints::MAX_LENGTH_PAYMENT_ADDRESS) {
- throw new AtolPaymentAddressTooLongException($payment_address, Constraints::MAX_LENGTH_PAYMENT_ADDRESS);
- }
- $this->payment_address = $payment_address;
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- return [
- 'email' => $this->getEmail(),
- 'sno' => $this->getSno(),
- 'inn' => $this->getInn(),
- 'payment_address' => $this->getPaymentAddress(),
- ];
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/CorrectionInfo.php b/src/AtolOnline/Entities/CorrectionInfo.php
deleted file mode 100644
index 2cbde55..0000000
--- a/src/AtolOnline/Entities/CorrectionInfo.php
+++ /dev/null
@@ -1,171 +0,0 @@
-setType($type);
- }
- if ($base_date) {
- $this->setDate($base_date);
- }
- if ($base_number) {
- $this->setNumber($base_number);
- }
- if ($base_name) {
- $this->setName($base_name);
- }
- }
-
- /**
- * Возвращает номер документа основания для коррекции.
- * Тег ФФД - 1179.
- *
- * @return string|null
- */
- public function getNumber(): ?string
- {
- return $this->base_number;
- }
-
- /**
- * Устанавливает номер документа основания для коррекции.
- * Тег ФФД - 1179.
- *
- * @param string $number
- * @return $this
- */
- public function setNumber(string $number)
- {
- $this->base_number = trim($number);
- return $this;
- }
-
- /**
- * Возвращает описание коррекции.
- * Тег ФФД - 1177.
- *
- * @return string|null
- */
- public function getName(): ?string
- {
- return $this->base_name;
- }
-
- /**
- * Устанавливает описание коррекции.
- * Тег ФФД - 1177.
- *
- * @param string $name
- * @return $this
- */
- public function setName(string $name)
- {
- $this->base_name = trim($name);
- return $this;
- }
-
- /**
- * Возвращает дату документа основания для коррекции.
- * Тег ФФД - 1178.
- *
- * @return string|null
- */
- public function getDate(): ?string
- {
- return $this->base_date;
- }
-
- /**
- * Устанавливает дату документа основания для коррекции.
- * Тег ФФД - 1178.
- *
- * @param string $date Строка в формате d.m.Y
- * @return $this
- */
- public function setDate(string $date)
- {
- $this->base_date = $date;
- return $this;
- }
-
- /**
- * Возвращает тип коррекции.
- * Тег ФФД - 1173.
- *
- * @return string|null
- */
- public function getType(): ?string
- {
- return $this->type;
- }
-
- /**
- * Устанавливает тип коррекции.
- * Тег ФФД - 1173.
- *
- * @param string $type
- * @return $this
- */
- public function setType(string $type)
- {
- $this->type = $type;
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- return [
- 'type' => $this->getType() ?? '', // обязателен
- 'base_date' => $this->getDate() ?? '', // обязателен
- 'base_number' => $this->getNumber() ?? '', // обязателен
- 'base_name' => $this->getName() ?? '' // не обязателен
- ];
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/Document.php b/src/AtolOnline/Entities/Document.php
deleted file mode 100644
index 66716fd..0000000
--- a/src/AtolOnline/Entities/Document.php
+++ /dev/null
@@ -1,452 +0,0 @@
-vats = new VatArray();
- $this->payments = new PaymentArray();
- $this->items = new ItemArray();
- }
-
- /**
- * Удаляет все налоги из документа и предметов расчёта
- *
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException Слишком большая сумма
- * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
- */
- public function clearVats()
- {
- $this->setVats([]);
- return $this;
- }
-
- /**
- * Добавляет новую ставку НДС в массив ставок НДС
- *
- * @param \AtolOnline\Entities\Vat $vat Объект ставки НДС
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
- */
- public function addVat(Vat $vat)
- {
- $this->vats->add($vat);
- return $this;
- }
-
- /**
- * Возвращает массив ставок НДС
- *
- * @return \AtolOnline\Entities\Vat[]
- */
- public function getVats(): array
- {
- return $this->vats->get();
- }
-
- /**
- * Устанавливает массив ставок НДС
- *
- * @param \AtolOnline\Entities\Vat[] $vats Массив ставок НДС
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolTooManyVatsException Слишком много ставок НДС
- * @throws \Exception
- */
- public function setVats(array $vats)
- {
- $this->vats->set($vats);
- return $this;
- }
-
- /**
- * Добавляет новую оплату в массив оплат
- *
- * @param \AtolOnline\Entities\Payment $payment Объект оплаты
- * @return $this
- * @throws \Exception
- * @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат
- */
- public function addPayment(Payment $payment)
- {
- if (count($this->getPayments()) == 0 && !$payment->getSum()) {
- $payment->setSum($this->calcTotal());
- }
- $this->payments->add($payment);
- return $this;
- }
-
- /**
- * Возвращает массив оплат
- *
- * @return \AtolOnline\Entities\Payment[]
- */
- public function getPayments(): array
- {
- return $this->payments->get();
- }
-
- /**
- * Устанавливает массив оплат
- *
- * @param \AtolOnline\Entities\Payment[] $payments Массив оплат
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException Слишком много оплат
- */
- public function setPayments(array $payments)
- {
- $this->payments->set($payments);
- return $this;
- }
-
- /**
- * Добавляет новый предмет расчёта в массив предметов расчёта
- *
- * @param \AtolOnline\Entities\Item $item Объект предмета расчёта
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
- */
- public function addItem(Item $item)
- {
- $this->items->add($item);
- return $this;
- }
-
- /**
- * Возвращает массив предметов расчёта
- *
- * @return \AtolOnline\Entities\Item[]
- */
- public function getItems(): array
- {
- return $this->items->get();
- }
-
- /**
- * Устанавливает массив предметов расчёта
- *
- * @param \AtolOnline\Entities\Item[] $items Массив предметов расчёта
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolTooManyItemsException Слишком много предметов расчёта
- */
- public function setItems(array $items)
- {
- $this->items->set($items);
- return $this;
- }
-
- /**
- * Возвращает заданного клиента (покупателя)
- *
- * @return Client|null
- */
- public function getClient(): ?Client
- {
- return $this->client;
- }
-
- /**
- * Устанавливает клиента (покупателя)
- *
- * @param Client|null $client
- * @return $this
- */
- public function setClient(?Client $client)
- {
- $this->client = $client;
- return $this;
- }
-
- /**
- * Возвращает заданную компанию (продавца)
- *
- * @return Company|null
- */
- public function getCompany(): ?Company
- {
- return $this->company;
- }
-
- /**
- * Устанавливает компанию (продавца)
- *
- * @param Company|null $company
- * @return $this
- */
- public function setCompany(?Company $company)
- {
- $this->company = $company;
- return $this;
- }
-
- /**
- * Возвращает ФИО кассира. Тег ФФД - 1021.
- *
- * @return string|null
- */
- public function getCashier(): ?string
- {
- return $this->cashier;
- }
-
- /**
- * Устанавливает ФИО кассира. Тег ФФД - 1021.
- *
- * @param string|null $cashier
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolCashierTooLongException
- */
- public function setCashier(?string $cashier)
- {
- if ($cashier !== null) {
- $cashier = trim($cashier);
- if (valid_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
- throw new AtolCashierTooLongException($cashier, Constraints::MAX_LENGTH_CASHIER_NAME);
- }
- }
- $this->cashier = $cashier;
- return $this;
- }
-
- /**
- * Возвращает данные коррекции
- *
- * @return \AtolOnline\Entities\CorrectionInfo|null
- */
- public function getCorrectionInfo(): ?CorrectionInfo
- {
- return $this->correction_info;
- }
-
- /**
- * Устанавливает данные коррекции
- *
- * @param \AtolOnline\Entities\CorrectionInfo|null $correction_info
- * @return $this
- */
- public function setCorrectionInfo(?CorrectionInfo $correction_info)
- {
- $this->correction_info = $correction_info;
- return $this;
- }
-
- /**
- * Пересчитывает, сохраняет и возвращает итоговую сумму чека по всем позициям (включая НДС). Тег ФФД - 1020.
- *
- * @return float
- * @throws \Exception
- */
- public function calcTotal()
- {
- $sum = 0;
- $this->clearVats();
- foreach ($this->items->get() as $item) {
- $sum += $item->calcSum();
- $this->addVat(new Vat($item->getVat()->getType(), $item->getSum()));
- }
- return $this->total = round($sum, 2);
- }
-
- /**
- * Возвращает итоговую сумму чека. Тег ФФД - 1020.
- *
- * @return float
- */
- public function getTotal(): float
- {
- return $this->total;
- }
-
- /**
- * Собирает объект документа из сырой json-строки
- *
- * @param string $json
- * @return \AtolOnline\Entities\Document
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException
- * @throws \AtolOnline\Exceptions\AtolException
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
- * @throws \AtolOnline\Exceptions\AtolInvalidJsonException
- * @throws \AtolOnline\Exceptions\AtolNameTooLongException
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException
- * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException
- * @throws \AtolOnline\Exceptions\AtolTooManyException
- * @throws \AtolOnline\Exceptions\AtolTooManyItemsException
- * @throws \AtolOnline\Exceptions\AtolTooManyPaymentsException
- * @throws \AtolOnline\Exceptions\AtolUnitTooLongException
- * @throws \AtolOnline\Exceptions\AtolUserdataTooLongException
- */
- public static function fromRaw(string $json)
- {
- $array = json_decode($json, true);
- if (json_last_error() !== JSON_ERROR_NONE) {
- throw new AtolInvalidJsonException();
- }
- $doc = new self();
- if (isset($array['company'])) {
- $doc->setCompany(new Company(
- $array['company']['sno'] ?? null,
- $array['company']['inn'] ?? null,
- $array['company']['payment_address'] ?? null,
- $array['company']['email'] ?? null
- ));
- }
- if (isset($array['client'])) {
- $doc->setClient(new Client(
- $array['client']['name'] ?? null,
- $array['client']['phone'] ?? null,
- $array['client']['email'] ?? null,
- $array['client']['inn'] ?? null
- ));
- }
- if (isset($array['correction_info'])) {
- $doc->setCorrectionInfo(new CorrectionInfo(
- $array['correction_info']['type'] ?? null,
- $array['correction_info']['base_date'] ?? null,
- $array['correction_info']['base_number'] ?? null,
- $array['correction_info']['base_name'] ?? null,
- ));
- }
- if (isset($array['items'])) {
- foreach ($array['items'] as $ar_item) {
- $item = new Item(
- $ar_item['name'] ?? null,
- $ar_item['price'] ?? null,
- $ar_item['quantity'] ?? null,
- $ar_item['measurement_unit'] ?? null,
- $ar_item['vat']['type'] ?? null,
- $ar_item['payment_object'] ?? null,
- $ar_item['payment_method'] ?? null
- );
- if (!empty($ar_item['user_data'])) {
- $item->setUserData($ar_item['user_data'] ?? null);
- }
- $doc->addItem($item);
- }
- }
- if (isset($array['payments'])) {
- foreach ($array['payments'] as $ar_payment) {
- $payment = new Payment();
- if (isset($ar_payment['type'])) {
- $payment->setType($ar_payment['type']);
- }
- if (isset($ar_payment['sum'])) {
- $payment->setSum($ar_payment['sum']);
- }
- $doc->payments->add($payment);
- }
- }
- if (isset($array['vats'])) {
- foreach ($array['vats'] as $vat_payment) {
- $vat = new Vat();
- if (isset($vat_payment['type'])) {
- $vat->setType($vat_payment['type']);
- }
- if (isset($vat_payment['sum'])) {
- $vat->setSum($vat_payment['sum']);
- }
- $doc->vats->add($vat);
- }
- }
- if (isset($array['total']) && $array['total'] != $doc->calcTotal()) {
- throw new AtolException('Real total sum not equals to provided in JSON one');
- }
- return $doc;
- }
-
- /**
- * Возвращает массив для кодирования в json
- *
- * @throws \Exception
- */
- public function jsonSerialize()
- {
- if ($this->getCompany()) {
- $json['company'] = $this->getCompany()->jsonSerialize(); // обязательно
- }
- if ($this->getPayments()) {
- $json['payments'] = $this->payments->jsonSerialize(); // обязательно
- }
- if ($this->getCashier()) {
- $json['cashier'] = $this->getCashier();
- }
- if ($this->getCorrectionInfo()) {
- $json['correction_info'] = $this->getCorrectionInfo()->jsonSerialize(); // обязательно для коррекционных
- } else {
- if ($this->getClient()) {
- $json['client'] = $this->getClient()->jsonSerialize(); // обязательно для некоррекционных
- }
- if ($this->getItems()) {
- $json['items'] = $this->items->jsonSerialize(); // обязательно для некоррекционных
- }
- $json['total'] = $this->calcTotal(); // обязательно для некоррекционных
- }
- if ($this->getVats()) {
- $json['vats'] = $this->vats->jsonSerialize();
- }
- return $json;
- }
-}
diff --git a/src/AtolOnline/Entities/Entity.php b/src/AtolOnline/Entities/Entity.php
deleted file mode 100644
index 0a35c9d..0000000
--- a/src/AtolOnline/Entities/Entity.php
+++ /dev/null
@@ -1,28 +0,0 @@
-jsonSerialize(), JSON_UNESCAPED_UNICODE);
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/Item.php b/src/AtolOnline/Entities/Item.php
deleted file mode 100644
index fcbc72f..0000000
--- a/src/AtolOnline/Entities/Item.php
+++ /dev/null
@@ -1,397 +0,0 @@
-setName($name);
- }
- if ($price) {
- $this->setPrice($price);
- }
- if ($quantity) {
- $this->setQuantity($quantity);
- }
- if ($measurement_unit) {
- $this->setMeasurementUnit($measurement_unit);
- }
- if ($vat_type) {
- $this->setVatType($vat_type);
- }
- if ($payment_object) {
- $this->setPaymentObject($payment_object);
- }
- if ($payment_method) {
- $this->setPaymentMethod($payment_method);
- }
- }
-
- /**
- * Возвращает наименование. Тег ФФД - 1030.
- *
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * Устаналивает наименование. Тег ФФД - 1030.
- *
- * @param string $name Наименование
- * @return $this
- * @throws AtolNameTooLongException Слишком длинное имя/наименование
- */
- public function setName(string $name)
- {
- $name = trim($name);
- if (valid_strlen($name) > Constraints::MAX_LENGTH_ITEM_NAME) {
- throw new AtolNameTooLongException($name, Constraints::MAX_LENGTH_ITEM_NAME);
- }
- $this->name = $name;
- return $this;
- }
-
- /**
- * Возвращает цену в рублях. Тег ФФД - 1079.
- *
- * @return float
- */
- public function getPrice()
- {
- return self::toRub($this->price);
- }
-
- /**
- * Устанавливает цену в рублях. Тег ФФД - 1079.
- *
- * @param float $rubles Цена за одну единицу в рублях
- * @return $this
- * @throws AtolPriceTooHighException Слишком высокая цена за одну единицу
- */
- public function setPrice(float $rubles)
- {
- if ($rubles > 42949672.95) {
- throw new AtolPriceTooHighException($rubles, 42949672.95);
- }
- $this->price = self::toKop($rubles);
- $this->calcSum();
- return $this;
- }
-
- /**
- * Возвращает количество. Тег ФФД - 1023.
- *
- * @return float
- */
- public function getQuantity(): float
- {
- return $this->quantity;
- }
-
- /**
- * Устанавливает количество. Тег ФФД - 1023.
- *
- * @param float $quantity Количество
- * @param string|null $measurement_unit Единица измерения количества
- * @return $this
- * @throws AtolTooManyException Слишком большое количество
- * @throws AtolPriceTooHighException Слишком высокая общая стоимость
- * @throws AtolUnitTooLongException Слишком длинное название единицы измерения
- */
- public function setQuantity(float $quantity, string $measurement_unit = null)
- {
- $quantity = round($quantity, 3);
- if ($quantity > 99999.999) {
- throw new AtolTooManyException($quantity, 99999.999);
- }
- $this->quantity = $quantity;
- $this->calcSum();
- if ($measurement_unit) {
- $this->setMeasurementUnit($measurement_unit);
- }
- return $this;
- }
-
- /**
- * Возвращает заданную единицу измерения количества. Тег ФФД - 1197.
- *
- * @return string
- */
- public function getMeasurementUnit(): string
- {
- return $this->measurement_unit;
- }
-
- /**
- * Устанавливает единицу измерения количества. Тег ФФД - 1197.
- *
- * @param string $measurement_unit Единица измерения количества
- * @return $this
- * @throws AtolUnitTooLongException Слишком длинное название единицы измерения
- */
- public function setMeasurementUnit(string $measurement_unit)
- {
- $measurement_unit = trim($measurement_unit);
- if (valid_strlen($measurement_unit) > Constraints::MAX_LENGTH_MEASUREMENT_UNIT) {
- throw new AtolUnitTooLongException($measurement_unit, Constraints::MAX_LENGTH_MEASUREMENT_UNIT);
- }
- $this->measurement_unit = $measurement_unit;
- return $this;
- }
-
- /**
- * Возвращает признак способа оплаты. Тег ФФД - 1214.
- *
- * @return string
- */
- public function getPaymentMethod(): string
- {
- return $this->payment_method;
- }
-
- /**
- * Устанавливает признак способа оплаты. Тег ФФД - 1214.
- *
- * @param string $payment_method Признак способа оплаты
- * @return $this
- * @todo Проверка допустимых значений
- */
- public function setPaymentMethod(string $payment_method)
- {
- $this->payment_method = trim($payment_method);
- return $this;
- }
-
- /**
- * Возвращает признак предмета расчёта. Тег ФФД - 1212.
- *
- * @return string
- */
- public function getPaymentObject(): string
- {
- return $this->payment_object;
- }
-
- /**
- * Устанавливает признак предмета расчёта. Тег ФФД - 1212.
- *
- * @param string $payment_object Признак предмета расчёта
- * @return $this
- * @todo Проверка допустимых значений
- */
- public function setPaymentObject(string $payment_object)
- {
- $this->payment_object = trim($payment_object);
- return $this;
- }
-
- /**
- * Возвращает ставку НДС
- *
- * @return \AtolOnline\Entities\Vat|null
- */
- public function getVat(): ?Vat
- {
- return $this->vat;
- }
-
- /**
- * Устанавливает ставку НДС
- *
- * @param string|null $vat_type Тип ставки НДС. Передать null, чтобы удалить ставку.
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException
- */
- public function setVatType(?string $vat_type)
- {
- if ($vat_type) {
- $this->vat
- ? $this->vat->setType($vat_type)
- : $this->vat = new Vat($vat_type);
- } else {
- $this->vat = null;
- }
- $this->calcSum();
- return $this;
- }
-
- /**
- * Возвращает дополнительный реквизит. Тег ФФД - 1191.
- *
- * @return string|null
- */
- public function getUserData(): ?string
- {
- return $this->user_data;
- }
-
- /**
- * Устанавливает дополнительный реквизит. Тег ФФД - 1191.
- *
- * @param string $user_data Дополнительный реквизит. Тег ФФД - 1191.
- * @return $this
- * @throws AtolUserdataTooLongException Слишком длинный дополнительный реквизит
- */
- public function setUserData(string $user_data)
- {
- $user_data = trim($user_data);
- if (valid_strlen($user_data) > Constraints::MAX_LENGTH_USER_DATA) {
- throw new AtolUserdataTooLongException($user_data, Constraints::MAX_LENGTH_USER_DATA);
- }
- $this->user_data = $user_data;
- return $this;
- }
-
- /**
- * Возвращает стоимость. Тег ФФД - 1043.
- *
- * @return float
- */
- public function getSum(): float
- {
- return self::toRub($this->sum);
- }
-
- /**
- * Расчитывает стоимость и размер НДС на неё
- *
- * @return float
- * @throws AtolPriceTooHighException Слишком большая сумма
- */
- public function calcSum()
- {
- $sum = $this->quantity * $this->price;
- if (self::toRub($sum) > 42949672.95) {
- throw new AtolPriceTooHighException($sum, 42949672.95);
- }
- $this->sum = $sum;
- if ($this->vat) {
- $this->vat->setSum(self::toRub($sum));
- }
- return $this->getSum();
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- $json = [
- 'name' => $this->getName(), // обязательно
- 'price' => $this->getPrice(), // обязательно
- 'quantity' => $this->getQuantity(), // обязательно
- 'sum' => $this->getSum(), // обязательно
- 'measurement_unit' => $this->getMeasurementUnit(),
- 'payment_method' => $this->getPaymentMethod(),
- 'payment_object' => $this->getPaymentObject()
- //TODO nomenclature_code
- //TODO agent_info
- //TODO supplier_info
- //TODO excise
- //TODO country_code
- //TODO declaration_number
- ];
- if ($this->getVat()) {
- $json['vat'] = $this->getVat()->jsonSerialize();
- }
- if ($this->getUserData()) {
- $json['user_data'] = $this->getUserData();
- }
- return $json;
- }
-}
diff --git a/src/AtolOnline/Entities/ItemArray.php b/src/AtolOnline/Entities/ItemArray.php
deleted file mode 100644
index 059f243..0000000
--- a/src/AtolOnline/Entities/ItemArray.php
+++ /dev/null
@@ -1,113 +0,0 @@
-set($items);
- }
- }
-
- /**
- * Устанавливает массив предметов расчёта
- *
- * @param Item[] $items Массив предметов расчёта
- * @return $this
- * @throws AtolTooManyItemsException Слишком много предметов расчёта
- */
- public function set(array $items)
- {
- if ($this->validateCount($items)) {
- $this->items = $items;
- }
- return $this;
- }
-
- /**
- * Добавляет предмет расчёта в массив
- *
- * @param Item $item Объект предмета расчёта
- * @return $this
- * @throws AtolTooManyItemsException Слишком много предметов расчёта
- */
- public function add(Item $item)
- {
- if ($this->validateCount()) {
- $this->items[] = $item;
- }
- return $this;
- }
-
- /**
- * Возвращает массив предметов расчёта
- *
- * @return Item[]
- */
- public function get()
- {
- return $this->items;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- $result = [];
- foreach ($this->get() as $item) {
- $result[] = $item->jsonSerialize();
- }
- return $result;
- }
-
- /**
- * Проверяет количество предметов расчёта
- *
- * @param Item[]|null $items Если передать массив, то проверит количество его элементов.
- * Иначе проверит количество уже присвоенных элементов.
- * @return bool true если всё хорошо, иначе выбрасывает исключение
- * @throws AtolTooManyItemsException Слишком много предметов расчёта
- */
- protected function validateCount(?array $items = null): bool
- {
- if ((!empty($items) && count($items) >= self::MAX_COUNT) || count($this->items) >= self::MAX_COUNT) {
- throw new AtolTooManyItemsException(count($items), self::MAX_COUNT);
- }
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/Payment.php b/src/AtolOnline/Entities/Payment.php
deleted file mode 100644
index 27f8398..0000000
--- a/src/AtolOnline/Entities/Payment.php
+++ /dev/null
@@ -1,97 +0,0 @@
-setType($payment_type);
- $this->setSum($sum);
- }
-
- /**
- * Возвращает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
- *
- * @return int
- */
- public function getType(): int
- {
- return $this->type;
- }
-
- /**
- * Устанавливает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
- *
- * @param int $type
- * @return $this
- */
- public function setType(int $type)
- {
- $this->type = $type;
- return $this;
- }
-
- /**
- * Возвращает сумму оплаты
- *
- * @return float
- */
- public function getSum(): float
- {
- return $this->sum;
- }
-
- /**
- * Устанавливает сумму оплаты
- *
- * @param float $sum
- * @return $this
- */
- public function setSum(float $sum)
- {
- $this->sum = $sum;
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- return [
- 'type' => $this->getType(),
- 'sum' => $this->getSum(),
- ];
- }
-}
diff --git a/src/AtolOnline/Entities/PaymentArray.php b/src/AtolOnline/Entities/PaymentArray.php
deleted file mode 100644
index f30920a..0000000
--- a/src/AtolOnline/Entities/PaymentArray.php
+++ /dev/null
@@ -1,111 +0,0 @@
-set($payments);
- }
- }
-
- /**
- * Устанавливает массив оплат
- *
- * @param Payment[] $payments
- * @return $this
- * @throws AtolTooManyPaymentsException Слишком много оплат
- */
- public function set(array $payments)
- {
- if ($this->validateCount($payments)) {
- $this->payments = $payments;
- }
- return $this;
- }
-
- /**
- * Добавляет новую оплату к заданным
- *
- * @param Payment $payment Объект оплаты
- * @return $this
- * @throws AtolTooManyPaymentsException Слишком много оплат
- */
- public function add(Payment $payment)
- {
- if ($this->validateCount()) {
- $this->payments[] = $payment;
- }
- return $this;
- }
-
- /**
- * Возвращает массив оплат
- *
- * @return Payment[]
- */
- public function get()
- {
- return $this->payments;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- $result = [];
- foreach ($this->get() as $payment) {
- $result[] = $payment->jsonSerialize();
- }
- return $result;
- }
-
- /**
- * Проверяет количество налоговых ставок
- *
- * @param Payment[]|null $payments Если передать массив, то проверит количество его элементов.
- * Иначе проверит количество уже присвоенных элементов.
- * @return bool true если всё хорошо, иначе выбрасывает исключение
- * @throws AtolTooManyPaymentsException Слишком много оплат
- */
- protected function validateCount(?array $payments = null): bool
- {
- if ((!empty($payments) && count($payments) >= self::MAX_COUNT) || count($this->payments) >= self::MAX_COUNT) {
- throw new AtolTooManyPaymentsException(count($payments), self::MAX_COUNT);
- }
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/Vat.php b/src/AtolOnline/Entities/Vat.php
deleted file mode 100644
index bd76ca1..0000000
--- a/src/AtolOnline/Entities/Vat.php
+++ /dev/null
@@ -1,188 +0,0 @@
-type = $type;
- if ($rubles) {
- $this->setSum($rubles);
- }
- }
-
- /**
- * Устанавливает:
- * размер НДС от суммы в копейках
- *
- * @param string $type Тип ставки НДС
- * @param int $kopeks Копейки
- * @return float|int
- * @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/
- * @see https://glavkniga.ru/situations/k500734
- * @see https://www.b-kontur.ru/nds-kalkuljator-online
- */
- protected static function calculator(string $type, int $kopeks)
- {
- switch ($type) {
- case VatTypes::NONE:
- case VatTypes::VAT0:
- return 0;
- case VatTypes::VAT10:
- //return $kopeks * 10 / 100;
- case VatTypes::VAT110:
- return $kopeks * 10 / 110;
- case VatTypes::VAT18:
- //return $kopeks * 18 / 100;
- case VatTypes::VAT118:
- return $kopeks * 18 / 118;
- case VatTypes::VAT20:
- //return $kopeks * 20 / 100;
- case VatTypes::VAT120:
- return $kopeks * 20 / 120;
- }
- return 0;
- }
-
- /**
- * Возвращает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
- *
- * @return string
- */
- public function getType(): string
- {
- return $this->type;
- }
-
- /**
- * Устанавливает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
- * Автоматически пересчитывает итоговый размер НДС от исходной суммы.
- *
- * @param string $type Тип ставки НДС
- * @return $this
- */
- public function setType(string $type)
- {
- $this->type = $type;
- $this->setFinal();
- return $this;
- }
-
- /**
- * Возвращает расчитанный итоговый размер ставки НДС в рублях. Тег ФФД - 1200.
- *
- * @return float
- */
- public function getFinalSum()
- {
- return self::toRub($this->sum_final);
- }
-
- /**
- * Устанавливает исходную сумму, от которой будет расчитываться итоговый размер НДС.
- * Автоматически пересчитывает итоговый размер НДС от исходной суммы.
- *
- * @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС
- * @return $this
- */
- public function setSum(float $rubles)
- {
- $this->sum_original = self::toKop($rubles);
- $this->setFinal();
- return $this;
- }
-
- /**
- * Возвращает исходную сумму, от которой расчитывается размер налога
- *
- * @return float
- */
- public function getSum(): float
- {
- return self::toRub($this->sum_original);
- }
-
- /**
- * Прибавляет указанную сумму к общей исходной сумме.
- * Автоматически пересчитывает итоговый размер НДС от новой исходной суммы.
- *
- * @param float $rubles
- * @return $this
- */
- public function addSum(float $rubles)
- {
- $this->sum_original += self::toKop($rubles);
- $this->setFinal();
- return $this;
- }
-
- /**
- * Расчитывает и возвращает размер НДС от указанной суммы в рублях.
- * Не изменяет итоговый размер НДС.
- *
- * @param float|null $rubles
- * @return float
- */
- public function calc(float $rubles): float
- {
- return self::toRub(self::calculator($this->type, self::toKop($rubles)));
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- return [
- 'type' => $this->getType(),
- 'sum' => $this->getFinalSum(),
- ];
- }
-
- /**
- * Расчитывает и устанавливает итоговый размер ставки от исходной суммы в копейках
- */
- protected function setFinal()
- {
- $this->sum_final = self::calculator($this->type, $this->sum_original);
- return $this;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Entities/VatArray.php b/src/AtolOnline/Entities/VatArray.php
deleted file mode 100644
index c292127..0000000
--- a/src/AtolOnline/Entities/VatArray.php
+++ /dev/null
@@ -1,115 +0,0 @@
-set($vats);
- }
- }
-
- /**
- * Устанавливает массив ставок НДС
- *
- * @param Vat[] $vats Массив ставок НДС
- * @return $this
- * @throws AtolTooManyVatsException Слишком много ставок НДС
- */
- public function set(array $vats)
- {
- if ($this->validateCount($vats)) {
- $this->vats = $vats;
- }
- return $this;
- }
-
- /**
- * Добавляет новую ставку НДС в массив
- *
- * @param Vat $vat Объект ставки НДС
- * @return $this
- * @throws AtolTooManyVatsException Слишком много ставок НДС
- */
- public function add(Vat $vat)
- {
- if ($this->validateCount()) {
- if (isset($this->vats[$vat->getType()])) {
- $this->vats[$vat->getType()]->addSum($vat->getSum());
- } else {
- $this->vats[$vat->getType()] = $vat;
- }
- }
- return $this;
- }
-
- /**
- * Возвращает массив ставок НДС
- *
- * @return Vat[]
- */
- public function get()
- {
- return $this->vats;
- }
-
- /**
- * @inheritDoc
- */
- public function jsonSerialize()
- {
- $result = [];
- foreach ($this->get() as $vat) {
- $result[] = $vat->jsonSerialize();
- }
- return $result;
- }
-
- /**
- * Проверяет количество налоговых ставок
- *
- * @param Vat[]|null $vats Если передать массив, то проверит количество его элементов.
- * Иначе проверит количество уже присвоенных элементов.
- * @return bool true если всё хорошо, иначе выбрасывает исключение
- * @throws AtolTooManyVatsException Слишком много ставок НДС
- */
- protected function validateCount(?array $vats = null): bool
- {
- if ((!empty($vats) && count($vats) >= self::MAX_COUNT) || count($this->vats) >= self::MAX_COUNT) {
- throw new AtolTooManyVatsException(count($vats), self::MAX_COUNT);
- }
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Exceptions/AtolAuthFailedException.php b/src/AtolOnline/Exceptions/AtolAuthFailedException.php
deleted file mode 100644
index 5cf16b3..0000000
--- a/src/AtolOnline/Exceptions/AtolAuthFailedException.php
+++ /dev/null
@@ -1,41 +0,0 @@
-isValid()
- ? $message
- : '['.$last_response->error->code.'] '.$last_response->error->text.
- '. ERROR_ID: '.$last_response->error->error_id.
- '. TYPE: '.$last_response->error->type;
- $code = $last_response->isValid() ? $code : $last_response->error->code;
- parent::__construct($message, $code, $previous);
- }
-}
diff --git a/src/AtolOnline/Exceptions/AtolCashierTooLongException.php b/src/AtolOnline/Exceptions/AtolCashierTooLongException.php
deleted file mode 100644
index 3f0ec20..0000000
--- a/src/AtolOnline/Exceptions/AtolCashierTooLongException.php
+++ /dev/null
@@ -1,30 +0,0 @@
-message;
- if ($this->getFfdTags()) {
- $message .= ' [FFD tags: '.implode(', ', $this->getFfdTags()).']';
- }
- parent::__construct($message, $code, $previous);
- }
-
- /**
- * Возвращает теги ФФД, с которыми связано исключение
- *
- * @return array|null
- */
- protected function getFfdTags(): ?array
- {
- return $this->ffd_tags;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Exceptions/AtolInnWrongLengthException.php b/src/AtolOnline/Exceptions/AtolInnWrongLengthException.php
deleted file mode 100644
index 2cd2ac5..0000000
--- a/src/AtolOnline/Exceptions/AtolInnWrongLengthException.php
+++ /dev/null
@@ -1,44 +0,0 @@
-message.' (max length - '.$max.', actual length - '.
- valid_strlen($string), $code, $previous);
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Exceptions/AtolTooManyException.php b/src/AtolOnline/Exceptions/AtolTooManyException.php
deleted file mode 100644
index 427c96b..0000000
--- a/src/AtolOnline/Exceptions/AtolTooManyException.php
+++ /dev/null
@@ -1,40 +0,0 @@
-message.' (max - '.$max.', actual - '.$quantity.')';
- parent::__construct($message, $code, $previous);
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Exceptions/AtolTooManyPaymentsException.php b/src/AtolOnline/Exceptions/AtolTooManyPaymentsException.php
deleted file mode 100644
index 14f483d..0000000
--- a/src/AtolOnline/Exceptions/AtolTooManyPaymentsException.php
+++ /dev/null
@@ -1,33 +0,0 @@
-email;
- }
-
- /**
- * Устанавливает почту. Тег ФФД: 1008, 1117.
- *
- * @param string $email
- * @return $this
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException Слишком длинный email
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException Невалидный email
- */
- public function setEmail(string $email)
- {
- $email = trim($email);
- if (valid_strlen($email) > Constraints::MAX_LENGTH_EMAIL) {
- throw new AtolEmailTooLongException($email, Constraints::MAX_LENGTH_EMAIL);
- } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
- throw new AtolEmailValidateException($email);
- }
- $this->email = $email;
- return $this;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Traits/HasInn.php b/src/AtolOnline/Traits/HasInn.php
deleted file mode 100644
index 856055a..0000000
--- a/src/AtolOnline/Traits/HasInn.php
+++ /dev/null
@@ -1,54 +0,0 @@
-inn ?? '';
- }
-
- /**
- * Устанавливает ИНН. Тег ФФД: 1228, 1018.
- * Входная строка лишается всех знаков, кроме цифр.
- *
- * @param string $inn
- * @return $this
- * @throws AtolInnWrongLengthException Некорректная длина ИНН
- */
- public function setInn(string $inn)
- {
- $inn = preg_replace("/[^0-9]/", '', $inn);
- if (preg_match_all(Constraints::PATTERN_INN, $inn) == 0) {
- throw new AtolInnWrongLengthException($inn);
- }
- $this->inn = $inn;
- return $this;
- }
-}
\ No newline at end of file
diff --git a/src/AtolOnline/Traits/RublesKopeksConverter.php b/src/AtolOnline/Traits/RublesKopeksConverter.php
deleted file mode 100644
index 95ecc78..0000000
--- a/src/AtolOnline/Traits/RublesKopeksConverter.php
+++ /dev/null
@@ -1,40 +0,0 @@
- копейки
- *
- * @package AtolOnline\Traits
- */
-trait RublesKopeksConverter
-{
- /**
- * Конвертирует рубли в копейки, учитывая только 2 знака после запятой
- *
- * @param float|null $rubles Рубли
- * @return int Копейки
- */
- protected static function toKop(?float $rubles = null)
- {
- return $rubles === null ? null : (int)round($rubles * 100, 2);
- }
-
- /**
- * Конвертирует копейки в рубли, оставляя только 2 знака после запятой
- *
- * @param int|null $kopeks Копейки
- * @return float Рубли
- */
- protected static function toRub(?int $kopeks = null)
- {
- return $kopeks === null ? null : round($kopeks / 100, 2);
- }
-}
diff --git a/src/Collections/EntityCollection.php b/src/Collections/EntityCollection.php
new file mode 100644
index 0000000..7c92f0d
--- /dev/null
+++ b/src/Collections/EntityCollection.php
@@ -0,0 +1,68 @@
+checkCount();
+ $this->checkItemsClasses();
+ return parent::jsonSerialize();
+ }
+
+ /**
+ * Проверяет количество элементов коллекции
+ *
+ * @return void
+ */
+ public function checkCount(): void
+ {
+ $this->isEmpty() && throw new (static::EMPTY_EXCEPTION_CLASS)();
+ $this->count() > static::MAX_COUNT && throw new (static::TOO_MANY_EXCEPTION_CLASS)(static::MAX_COUNT);
+ }
+
+ /**
+ * Проверяет корректность класса элемента коллекции
+ *
+ * @param mixed $item
+ * @return void
+ * @throws InvalidEntityInCollectionException
+ */
+ public function checkItemClass(mixed $item): void
+ {
+ if (!is_object($item) || $item::class !== static::ENTITY_CLASS) {
+ throw new InvalidEntityInCollectionException(static::class, static::ENTITY_CLASS, $item);
+ }
+ }
+
+ /**
+ * Проверяет корректность классов элементов коллекции
+ *
+ * @return $this
+ * @throws InvalidEntityInCollectionException
+ */
+ public function checkItemsClasses(): self
+ {
+ return $this->each(fn($item) => $this->checkItemClass($item));
+ }
+}
diff --git a/src/Collections/Items.php b/src/Collections/Items.php
new file mode 100644
index 0000000..62a1d06
--- /dev/null
+++ b/src/Collections/Items.php
@@ -0,0 +1,41 @@
+setName($name)->setValue($value);
+ }
+
+ /**
+ * Возвращает наименование реквизита
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Устанавливает наименование реквизита
+ *
+ * @param string $name
+ * @return $this
+ * @throws TooLongAddUserPropNameException
+ * @throws EmptyAddUserPropNameException
+ */
+ public function setName(string $name): self
+ {
+ $name = trim($name);
+ if (mb_strlen($name) > Constraints::MAX_LENGTH_ADD_USER_PROP_NAME) {
+ throw new TooLongAddUserPropNameException($name);
+ }
+ if (empty($name)) {
+ throw new EmptyAddUserPropNameException($name);
+ }
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный телефон
+ *
+ * @return string|null
+ */
+ public function getValue(): ?string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Устанавливает значение реквизита
+ *
+ * @param string $value
+ * @return $this
+ * @throws TooLongAddUserPropValueException
+ * @throws EmptyAddUserPropValueException
+ */
+ public function setValue(string $value): self
+ {
+ $value = trim($value);
+ if (mb_strlen($value) > Constraints::MAX_LENGTH_CLIENT_NAME) {
+ throw new TooLongAddUserPropValueException($value);
+ }
+ if (empty($value)) {
+ throw new EmptyAddUserPropValueException($value);
+ }
+ $this->value = $value;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ #[ArrayShape(['name' => 'string', 'value' => 'null|string'])]
+ public function jsonSerialize(): array
+ {
+ return [
+ 'name' => $this->getName(),
+ 'value' => $this->getValue(),
+ ];
+ }
+}
diff --git a/src/Entities/AgentInfo.php b/src/Entities/AgentInfo.php
new file mode 100644
index 0000000..4607734
--- /dev/null
+++ b/src/Entities/AgentInfo.php
@@ -0,0 +1,169 @@
+setType($type);
+ !is_null($pagent) && $this->setPayingAgent($pagent);
+ !is_null($rp_operator) && $this->setReceivePaymentsOperator($rp_operator);
+ !is_null($mt_operator) && $this->setMoneyTransferOperator($mt_operator);
+ }
+
+ /**
+ * Возвращает установленный признак оператора
+ *
+ * @return string|null
+ */
+ public function getType(): ?string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Устанавливает признак оператора
+ *
+ * @param string|null $type
+ * @return AgentInfo
+ * @throws InvalidEnumValueException
+ */
+ public function setType(?string $type): self
+ {
+ AgentTypes::isValid($type) && $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Взвращает установленного платёжного агента
+ *
+ * @return PayingAgent|null
+ */
+ public function getPayingAgent(): ?PayingAgent
+ {
+ return $this->paying_agent;
+ }
+
+ /**
+ * Устанавливает платёжного агента
+ *
+ * @param PayingAgent|null $agent
+ * @return AgentInfo
+ */
+ public function setPayingAgent(?PayingAgent $agent): self
+ {
+ $this->paying_agent = $agent;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного оператора по приёму платежей
+ *
+ * @return ReceivePaymentsOperator|null
+ */
+ public function getReceivePaymentsOperator(): ?ReceivePaymentsOperator
+ {
+ return $this->receive_payments_operator;
+ }
+
+ /**
+ * Устанавливает оператора по приёму платежей
+ *
+ * @param ReceivePaymentsOperator|null $operator
+ * @return AgentInfo
+ */
+ public function setReceivePaymentsOperator(?ReceivePaymentsOperator $operator): self
+ {
+ $this->receive_payments_operator = $operator;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного оператора перевода
+ *
+ * @return MoneyTransferOperator|null
+ */
+ public function getMoneyTransferOperator(): ?MoneyTransferOperator
+ {
+ return $this->money_transfer_operator;
+ }
+
+ /**
+ * Устанавливает оператора перевода
+ *
+ * @param MoneyTransferOperator|null $operator
+ * @return AgentInfo
+ */
+ public function setMoneyTransferOperator(?MoneyTransferOperator $operator): self
+ {
+ $this->money_transfer_operator = $operator;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ $this->getType() && $json['type'] = $this->getType();
+ $this->getPayingAgent()?->jsonSerialize() && $json['paying_agent'] = $this
+ ->getPayingAgent()->jsonSerialize();
+ $this->getReceivePaymentsOperator()?->jsonSerialize() && $json['receive_payments_operator'] = $this
+ ->getReceivePaymentsOperator()->jsonSerialize();
+ $this->getMoneyTransferOperator()?->jsonSerialize() && $json['money_transfer_operator'] = $this
+ ->getMoneyTransferOperator()->jsonSerialize();
+ return $json;
+ }
+}
diff --git a/src/Entities/Client.php b/src/Entities/Client.php
new file mode 100644
index 0000000..8011274
--- /dev/null
+++ b/src/Entities/Client.php
@@ -0,0 +1,141 @@
+setName($name);
+ !is_null($email) && $this->setEmail($email);
+ !is_null($phone) && $this->setPhone($phone);
+ !is_null($inn) && $this->setInn($inn);
+ }
+
+ /**
+ * Возвращает наименование покупателя
+ *
+ * @return string|null
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Устанавливает наименование покупателя
+ *
+ * @param string|null $name
+ * @return $this
+ * @throws TooLongClientNameException
+ */
+ public function setName(?string $name): self
+ {
+ if (is_string($name)) {
+ $name = preg_replace('/[\n\r\t]/', '', trim($name));
+ if (mb_strlen($name) > Constraints::MAX_LENGTH_CLIENT_NAME) {
+ throw new TooLongClientNameException($name);
+ }
+ }
+ $this->name = $name ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный телефон
+ *
+ * @return string|null
+ */
+ public function getPhone(): ?string
+ {
+ return $this->phone;
+ }
+
+ /**
+ * Устанавливает телефон
+ *
+ * @param string|null $phone Номер телефона
+ * @return $this
+ * @throws InvalidPhoneException
+ */
+ public function setPhone(?string $phone): self
+ {
+ if (is_string($phone)) {
+ $phone = preg_replace('/[^\d]/', '', trim($phone));
+ if (preg_match(Constraints::PATTERN_PHONE, $phone) != 1) {
+ throw new InvalidPhoneException($phone);
+ }
+ }
+ $this->phone = empty($phone) ? null : "+$phone";
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ !is_null($this->getName()) && $json['name'] = $this->getName();
+ !is_null($this->getEmail()) && $json['email'] = $this->getEmail();
+ !is_null($this->getPhone()) && $json['phone'] = $this->getPhone();
+ !is_null($this->getInn()) && $json['inn'] = $this->getInn();
+ return $json;
+ }
+}
diff --git a/src/Entities/Company.php b/src/Entities/Company.php
new file mode 100644
index 0000000..e5b4b59
--- /dev/null
+++ b/src/Entities/Company.php
@@ -0,0 +1,156 @@
+setEmail($email)->setSno($sno)->setInn($inn)->setPaymentAddress($payment_address);
+ }
+
+ /**
+ * Возвращает установленный тип налогообложения
+ *
+ * @return string
+ */
+ public function getSno(): string
+ {
+ return $this->sno;
+ }
+
+ /**
+ * Устанавливает тип налогообложения
+ *
+ * @param string $sno
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setSno(string $sno): self
+ {
+ $sno = trim($sno);
+ SnoTypes::isValid($sno) && $this->sno = $sno;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный адрес места расчётов
+ *
+ * @return string
+ */
+ public function getPaymentAddress(): string
+ {
+ return $this->payment_address;
+ }
+
+ /**
+ * Устанавливает адрес места расчётов
+ *
+ * @param string $payment_address
+ * @return $this
+ * @throws TooLongPaymentAddressException
+ * @throws InvalidPaymentAddressException
+ */
+ public function setPaymentAddress(string $payment_address): self
+ {
+ $payment_address = trim($payment_address);
+ if (empty($payment_address)) {
+ throw new InvalidPaymentAddressException();
+ } elseif (mb_strlen($payment_address) > Constraints::MAX_LENGTH_PAYMENT_ADDRESS) {
+ throw new TooLongPaymentAddressException($payment_address);
+ }
+ $this->payment_address = $payment_address;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @throws InvalidEmailException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ */
+ #[ArrayShape([
+ 'email' => 'string',
+ 'sno' => 'string',
+ 'inn' => 'string',
+ 'payment_address' => 'string',
+ ])]
+ public function jsonSerialize(): array
+ {
+ return [
+ 'email' => $this->email
+ ? $this->getEmail()
+ : throw new InvalidEmailException(),
+ 'sno' => $this->sno
+ ? $this->getSno()
+ : throw new InvalidEnumValueException(SnoTypes::class, 'null'),
+ 'inn' => $this->inn
+ ? $this->getInn()
+ : throw new InvalidInnLengthException(),
+ 'payment_address' => $this->payment_address
+ ? $this->getPaymentAddress()
+ : throw new InvalidPaymentAddressException(),
+ ];
+ }
+}
diff --git a/src/Entities/Correction.php b/src/Entities/Correction.php
new file mode 100644
index 0000000..3e8fade
--- /dev/null
+++ b/src/Entities/Correction.php
@@ -0,0 +1,273 @@
+setCompany($company)->setCorrectionInfo($correction_info)->setPayments($payments)->setVats($vats);
+ }
+
+ /**
+ * Возвращает установленного продавца
+ *
+ * @return Company
+ */
+ public function getCompany(): Company
+ {
+ return $this->company;
+ }
+
+ /**
+ * Устанаваливает продавца
+ *
+ * @param Company $company
+ * @return $this
+ */
+ public function setCompany(Company $company): self
+ {
+ $this->company = $company;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного кассира
+ *
+ * @return string|null
+ */
+ public function getCashier(): ?string
+ {
+ return $this->cashier;
+ }
+
+ /**
+ * Устанаваливает кассира
+ *
+ * @param string|null $cashier
+ * @return $this
+ * @throws TooLongCashierException
+ */
+ public function setCashier(?string $cashier): self
+ {
+ if (is_string($cashier)) {
+ $cashier = trim($cashier);
+ if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
+ throw new TooLongCashierException($cashier);
+ }
+ }
+ $this->cashier = $cashier ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленные данные коррекции
+ *
+ * @return CorrectionInfo
+ */
+ public function getCorrectionInfo(): CorrectionInfo
+ {
+ return $this->correction_info;
+ }
+
+ /**
+ * Устанавливает данные коррекции
+ *
+ * @param CorrectionInfo $correction_info
+ * @return Correction
+ */
+ public function setCorrectionInfo(CorrectionInfo $correction_info): Correction
+ {
+ $this->correction_info = $correction_info;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную коллекцию оплат
+ *
+ * @return Payments
+ */
+ public function getPayments(): Payments
+ {
+ return $this->payments;
+ }
+
+ /**
+ * Устанаваливает коллекцию оплат
+ *
+ * @param Payments $payments
+ * @return $this
+ * @throws InvalidEntityInCollectionException
+ */
+ public function setPayments(Payments $payments): self
+ {
+ $payments->checkCount();
+ $payments->checkItemsClasses();
+ $this->payments = $payments;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную коллекцию ставок НДС
+ *
+ * @return Vats|null
+ */
+ public function getVats(): Vats
+ {
+ return $this->vats ?? new Vats();
+ }
+
+ /**
+ * Устанаваливает коллекцию ставок НДС
+ *
+ * @param Vats|null $vats
+ * @return $this
+ * @throws Exception
+ */
+ public function setVats(Vats $vats): self
+ {
+ $vats->checkCount();
+ $vats->checkItemsClasses();
+ $this->vats = $vats;
+ return $this;
+ }
+
+ /**
+ * Регистрирует коррекцию прихода по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sellCorrect(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->sellCorrect($this, $external_id);
+ }
+
+ /**
+ * Регистрирует коррекцию расхода по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buyCorrect(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->buyCorrect($this, $external_id);
+ }
+
+ /**
+ * @inheritDoc
+ * @throws InvalidEntityInCollectionException
+ */
+ #[ArrayShape([
+ 'company' => '\AtolOnline\Entities\Company',
+ 'correction_info' => '\AtolOnline\Entities\CorrectionInfo',
+ 'payments' => 'array',
+ 'vats' => '\AtolOnline\Collections\Vats|null',
+ 'cashier' => 'null|string',
+ ])]
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'company' => $this->getCompany(),
+ 'correction_info' => $this->getCorrectionInfo(),
+ 'payments' => $this->getPayments()->jsonSerialize(),
+ 'vats' => $this->getVats(),
+ ];
+ !is_null($this->getCashier()) && $json['cashier'] = $this->getCashier();
+ return $json;
+ }
+}
diff --git a/src/Entities/CorrectionInfo.php b/src/Entities/CorrectionInfo.php
new file mode 100644
index 0000000..2402640
--- /dev/null
+++ b/src/Entities/CorrectionInfo.php
@@ -0,0 +1,155 @@
+setType($type)->setDate($date)->setNumber($number);
+ }
+
+ /**
+ * Возвращает тип коррекции
+ *
+ * @return string|null
+ */
+ public function getType(): ?string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Устанавливает тип коррекции
+ *
+ * @param string $type
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setType(string $type): self
+ {
+ $type = trim($type);
+ CorrectionTypes::isValid($type) && $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Возвращает дату документа основания для коррекции
+ *
+ * @return string|null
+ */
+ public function getDate(): ?string
+ {
+ return $this->date;
+ }
+
+ /**
+ * Устанавливает дату документа основания для коррекции
+ *
+ * @param DateTime|string $date Строковая дата в формате d.m.Y либо объект DateTime с датой
+ * @return $this
+ * @throws InvalidCorrectionDateException
+ */
+ public function setDate(DateTime|string $date): self
+ {
+ try {
+ if (is_string($date)) {
+ $date = new DateTime(trim($date));
+ }
+ $this->date = $date->format(Constraints::CORRECTION_DATE_FORMAT);
+ } catch (Exception $e) {
+ throw new InvalidCorrectionDateException($date, $e->getMessage());
+ }
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный номер документа основания для коррекции
+ *
+ * @return string|null
+ */
+ public function getNumber(): ?string
+ {
+ return $this->number;
+ }
+
+ /**
+ * Устанавливает номер документа основания для коррекции
+ *
+ * @param string $number
+ * @return $this
+ * @throws EmptyCorrectionNumberException
+ */
+ public function setNumber(string $number): self
+ {
+ $number = trim($number);
+ empty($number) && throw new EmptyCorrectionNumberException();
+ $this->number = $number;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ #[ArrayShape(['type' => 'string', 'base_date' => 'string', 'base_number' => 'string'])]
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => $this->getType(),
+ 'base_date' => $this->getDate(),
+ 'base_number' => $this->getNumber(),
+ ];
+ }
+}
diff --git a/src/Entities/Entity.php b/src/Entities/Entity.php
new file mode 100644
index 0000000..86a13c7
--- /dev/null
+++ b/src/Entities/Entity.php
@@ -0,0 +1,93 @@
+ "\AtolOnline\Entities\Company",
+ 'correction_info' => "\AtolOnline\Entities\CorrectionInfo",
+ 'payments' => "array",
+ 'vats' => "\AtolOnline\Collections\Vats|null",
+ 'cashier' => "null|string",
+ ])]
+ public function toArray()
+ {
+ return $this->jsonSerialize();
+ }
+
+ /**
+ * Возвращает строковое представление json-структуры объекта
+ *
+ * @return false|string
+ */
+ public function __toString()
+ {
+ return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return isset($this->toArray()[$offset]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->toArray()[$offset];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetSet(mixed $offset, mixed $value)
+ {
+ throw new BadMethodCallException(
+ 'Объект ' . static::class . ' нельзя изменять как массив. Следует использовать сеттеры.'
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new BadMethodCallException(
+ 'Объект ' . static::class . ' нельзя изменять как массив. Следует использовать сеттеры.'
+ );
+ }
+}
diff --git a/src/Entities/Item.php b/src/Entities/Item.php
new file mode 100644
index 0000000..d994a02
--- /dev/null
+++ b/src/Entities/Item.php
@@ -0,0 +1,594 @@
+setName($name);
+ !is_null($price) && $this->setPrice($price);
+ !is_null($quantity) && $this->setQuantity($quantity);
+ }
+
+ /**
+ * Возвращает наименование
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Устаналивает наименование
+ *
+ * @param string $name Наименование
+ * @return $this
+ * @throws TooLongItemNameException
+ * @throws EmptyItemNameException
+ */
+ public function setName(string $name): self
+ {
+ $name = trim($name);
+ if (mb_strlen($name) > Constraints::MAX_LENGTH_ITEM_NAME) {
+ throw new TooLongItemNameException($name);
+ }
+ if (empty($name)) {
+ throw new EmptyItemNameException();
+ }
+ $this->name = $name;
+ return $this;
+ }
+
+ /**
+ * Возвращает цену в рублях
+ *
+ * @return float
+ */
+ public function getPrice(): float
+ {
+ return $this->price;
+ }
+
+ /**
+ * Устанавливает цену в рублях
+ *
+ * @param float $price
+ * @return $this
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws TooHighItemSumException
+ */
+ public function setPrice(float $price): self
+ {
+ $price = round($price, 2);
+ if ($price > Constraints::MAX_COUNT_ITEM_PRICE) {
+ throw new TooHighItemPriceException($this->getName(), $price);
+ }
+ if ($price < 0) {
+ throw new NegativeItemPriceException($this->getName(), $price);
+ }
+ $this->price = $price;
+ $this->getVat()?->setSum($this->getSum());
+ return $this;
+ }
+
+ /**
+ * Возвращает количество
+ *
+ * @return float
+ */
+ public function getQuantity(): float
+ {
+ return $this->quantity;
+ }
+
+ /**
+ * Устанавливает количество
+ *
+ * @param float $quantity Количество
+ * @return $this
+ * @throws TooHighItemQuantityException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemSumException
+ */
+ public function setQuantity(float $quantity): self
+ {
+ $quantity = round($quantity, 3);
+ if ($quantity > Constraints::MAX_COUNT_ITEM_QUANTITY) {
+ throw new TooHighItemQuantityException($this->getName(), $quantity);
+ }
+ if ($quantity < 0) {
+ throw new NegativeItemQuantityException($this->getName(), $quantity);
+ }
+ $this->quantity = $quantity;
+ $this->getVat()?->setSum($this->getSum());
+ return $this;
+ }
+
+ /**
+ * Возвращает стоимость (цена * количество + акциз)
+ *
+ * @return float
+ * @throws TooHighItemSumException
+ */
+ public function getSum(): float
+ {
+ $sum = $this->getPrice() * $this->getQuantity() + (float)$this->getExcise();
+ if ($sum > Constraints::MAX_COUNT_ITEM_SUM) {
+ throw new TooHighItemSumException($this->getName(), $sum);
+ }
+ return $sum;
+ }
+
+ /**
+ * Возвращает заданную единицу измерения количества
+ *
+ * @return string|null
+ */
+ public function getMeasurementUnit(): ?string
+ {
+ return $this->measurement_unit;
+ }
+
+ /**
+ * Устанавливает единицу измерения количества
+ *
+ * @param string|null $measurement_unit
+ * @return $this
+ * @throws TooLongMeasurementUnitException
+ */
+ public function setMeasurementUnit(?string $measurement_unit): self
+ {
+ $measurement_unit = trim((string)$measurement_unit);
+ if (mb_strlen($measurement_unit) > Constraints::MAX_LENGTH_MEASUREMENT_UNIT) {
+ throw new TooLongMeasurementUnitException($measurement_unit);
+ }
+ $this->measurement_unit = $measurement_unit ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный код товара
+ *
+ * @return string|null
+ */
+ public function getCode(): ?string
+ {
+ return $this->code;
+ }
+
+ /**
+ * Возвращает шестнадцатиричное представление кода товара
+ *
+ * @return string|null
+ */
+ public function getCodeHex(): ?string
+ {
+ return $this->code_hex;
+ }
+
+ /**
+ * Устанавливает код товара
+ *
+ * @param string|null $code
+ * @return Item
+ * @throws TooLongItemCodeException
+ */
+ public function setCode(?string $code): self
+ {
+ $hex_string = null;
+ $code = trim((string)$code);
+ if (mb_strlen($code) > Constraints::MAX_LENGTH_ITEM_CODE) {
+ throw new TooLongItemCodeException($this->getName(), $code);
+ }
+ if (!empty($code)) {
+ $hex = bin2hex($code);
+ $hex_string = trim(preg_replace('/([\dA-Fa-f]{2})/', '$1 ', $hex));
+ }
+ $this->code = $code ?: null;
+ $this->code_hex = $hex_string ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает признак способа оплаты
+ *
+ * @return string|null
+ */
+ public function getPaymentMethod(): ?string
+ {
+ return $this->payment_method;
+ }
+
+ /**
+ * Устанавливает признак способа оплаты
+ *
+ * @param string|null $payment_method Признак способа оплаты
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setPaymentMethod(?string $payment_method): self
+ {
+ $payment_method = trim((string)$payment_method);
+ PaymentMethods::isValid($payment_method);
+ $this->payment_method = $payment_method ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает признак предмета расчёта
+ *
+ * @return string|null
+ */
+ public function getPaymentObject(): ?string
+ {
+ return $this->payment_object;
+ }
+
+ /**
+ * Устанавливает признак предмета расчёта
+ *
+ * @param string|null $payment_object Признак предмета расчёта
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setPaymentObject(?string $payment_object): self
+ {
+ $payment_object = trim((string)$payment_object);
+ PaymentObjects::isValid($payment_object);
+ $this->payment_object = $payment_object ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает ставку НДС
+ *
+ * @return Vat|null
+ */
+ public function getVat(): ?Vat
+ {
+ return $this->vat;
+ }
+
+ /**
+ * Устанавливает ставку НДС
+ *
+ * @param Vat|string|null $vat Объект ставки, одно из значений VatTypes или null для удаления ставки
+ * @return $this
+ * @throws TooHighItemSumException
+ * @throws InvalidEnumValueException
+ */
+ public function setVat(Vat|string|null $vat): self
+ {
+ if (is_string($vat)) {
+ $vat = trim($vat);
+ empty($vat)
+ ? $this->vat = null
+ : VatTypes::isValid($vat) && $this->vat = new Vat($vat, $this->getSum());
+ } elseif ($vat instanceof Vat) {
+ $vat->setSum($this->getSum());
+ $this->vat = $vat;
+ }
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный объект атрибутов агента
+ *
+ * @return AgentInfo|null
+ */
+ public function getAgentInfo(): ?AgentInfo
+ {
+ return $this->agent_info;
+ }
+
+ /**
+ * Устанавливает атрибуты агента
+ *
+ * @param AgentInfo|null $agent_info
+ * @return Item
+ */
+ public function setAgentInfo(?AgentInfo $agent_info): self
+ {
+ $this->agent_info = $agent_info;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного поставщика
+ *
+ * @return Supplier|null
+ */
+ public function getSupplier(): ?Supplier
+ {
+ return $this->supplier;
+ }
+
+ /**
+ * Устанавливает поставщика
+ *
+ * @param Supplier|null $supplier
+ * @return Item
+ */
+ public function setSupplier(?Supplier $supplier): self
+ {
+ $this->supplier = $supplier;
+ return $this;
+ }
+
+ /**
+ * Возвращает дополнительный реквизит
+ *
+ * @return string|null
+ */
+ public function getUserData(): ?string
+ {
+ return $this->user_data;
+ }
+
+ /**
+ * Устанавливает дополнительный реквизит
+ *
+ * @param string|null $user_data Дополнительный реквизит
+ * @return $this
+ * @throws TooLongUserdataException
+ */
+ public function setUserData(?string $user_data): self
+ {
+ $user_data = trim((string)$user_data);
+ if (mb_strlen($user_data) > Constraints::MAX_LENGTH_USER_DATA) {
+ throw new TooLongUserdataException($user_data);
+ }
+ $this->user_data = $user_data ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную сумму акциза
+ *
+ * @return float|null
+ */
+ public function getExcise(): ?float
+ {
+ return $this->excise;
+ }
+
+ /**
+ * Устанавливает сумму акциза
+ *
+ * @param float|null $excise
+ * @return Item
+ * @throws NegativeItemExciseException
+ * @throws TooHighItemSumException
+ */
+ public function setExcise(?float $excise): self
+ {
+ if ($excise < 0) {
+ throw new NegativeItemExciseException($this->getName(), $excise);
+ }
+ $this->excise = $excise;
+ $this->getVat()?->setSum($this->getSum());
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный код страны происхождения товара
+ *
+ * @return string|null
+ * @see https://ru.wikipedia.org/wiki/Общероссийский_классификатор_стран_мира
+ * @see https://classifikators.ru/oksm
+ */
+ public function getCountryCode(): ?string
+ {
+ return $this->country_code;
+ }
+
+ /**
+ * Устанавливает код страны происхождения товара
+ *
+ * @param string|null $country_code
+ * @return Item
+ * @throws InvalidOKSMCodeException
+ * @see https://classifikators.ru/oksm
+ * @see https://ru.wikipedia.org/wiki/Общероссийский_классификатор_стран_мира
+ */
+ public function setCountryCode(?string $country_code): self
+ {
+ $country_code = trim((string)$country_code);
+ if (preg_match(Constraints::PATTERN_OKSM_CODE, $country_code) != 1) {
+ throw new InvalidOKSMCodeException($country_code);
+ }
+ $this->country_code = $country_code ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный код таможенной декларации
+ *
+ * @return string|null
+ */
+ public function getDeclarationNumber(): ?string
+ {
+ return $this->declaration_number;
+ }
+
+ /**
+ * Устанавливает код таможенной декларации
+ *
+ * @param string|null $declaration_number
+ * @return Item
+ * @throws InvalidDeclarationNumberException
+ */
+ public function setDeclarationNumber(?string $declaration_number): self
+ {
+ if (is_string($declaration_number)) {
+ $declaration_number = trim($declaration_number);
+ if (
+ mb_strlen($declaration_number) < Constraints::MIN_LENGTH_DECLARATION_NUMBER
+ || mb_strlen($declaration_number) > Constraints::MAX_LENGTH_DECLARATION_NUMBER
+ ) {
+ throw new InvalidDeclarationNumberException($declaration_number);
+ }
+ }
+ $this->declaration_number = $declaration_number;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ * @throws TooHighItemSumException
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'name' => $this->getName(),
+ 'price' => $this->getPrice(),
+ 'quantity' => $this->getQuantity(),
+ 'sum' => $this->getSum(),
+ ];
+ !is_null($this->getMeasurementUnit()) && $json['measurement_unit'] = $this->getMeasurementUnit();
+ !is_null($this->getCodeHex()) && $json['nomenclature_code'] = $this->getCodeHex();
+ !is_null($this->getPaymentMethod()) && $json['payment_method'] = $this->getPaymentMethod();
+ !is_null($this->getPaymentObject()) && $json['payment_object'] = $this->getPaymentObject();
+ !is_null($this->getDeclarationNumber()) && $json['declaration_number'] = $this->getDeclarationNumber();
+ $this->getVat()?->jsonSerialize() && $json['vat'] = $this->getVat()->jsonSerialize();
+ $this->getAgentInfo()?->jsonSerialize() && $json['agent_info'] = $this->getAgentInfo()->jsonSerialize();
+ $this->getSupplier()?->jsonSerialize() && $json['supplier_info'] = $this->getSupplier()->jsonSerialize();
+ !is_null($this->getUserData()) && $json['user_data'] = $this->getUserData();
+ !is_null($this->getExcise()) && $json['excise'] = $this->getExcise();
+ !is_null($this->getCountryCode()) && $json['country_code'] = $this->getCountryCode();
+ return $json;
+ }
+}
diff --git a/src/Entities/Kkt.php b/src/Entities/Kkt.php
new file mode 100644
index 0000000..ba33358
--- /dev/null
+++ b/src/Entities/Kkt.php
@@ -0,0 +1,138 @@
+ 'Нет ошибок',
+ 1 => 'Отсутствует физический канал связи',
+ 2 => 'Ошибка сетевых настроек или нет соединения с сервером ОФД',
+ 3 => 'Разрыв соединения при передаче документа на сервер',
+ 4 => 'Некорректный заголовок сессионного пакета',
+ 5 => 'Превышен таймаут ожидания квитанции',
+ 6 => 'Разрыв соединения при приёме квитанции',
+ 7 => 'Превышен таймаут передачи документа на сервер',
+ 8 => 'ОФД-процесс не иницилизирован',
+ ];
+
+ /**
+ * @var string[] Список обязательных атрибутов
+ */
+ private array $properties = [
+ 'serialNumber',
+ 'registrationNumber',
+ 'deviceNumber',
+ 'fiscalizationDate',
+ 'fiscalStorageExpiration',
+ 'signedDocuments',
+ 'fiscalStoragePercentageUse',
+ 'fiscalStorageINN',
+ 'fiscalStorageSerialNumber',
+ 'fiscalStoragePaymentAddress',
+ 'groupCode',
+ 'timestamp',
+ 'isShiftOpened',
+ 'shiftNumber',
+ 'shiftReceipt',
+ //'unsentDocs',
+ //'firstUnsetDocTimestamp',
+ 'networkErrorCode',
+ ];
+
+ /**
+ * @var string[] Массив атрибутов, которые кастуются к DateTime
+ */
+ private array $timestamps = [
+ 'fiscalizationDate',
+ 'fiscalStorageExpiration',
+ 'firstUnsetDocTimestamp',
+ 'timestamp',
+ ];
+
+ /**
+ * Конструктор
+ *
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ */
+ public function __construct(protected object $data)
+ {
+ if (empty((array)$data)) {
+ throw new EmptyMonitorDataException();
+ }
+ $diff = array_diff($this->properties, array_keys((array)$data));
+ if (count($diff) !== 0) {
+ throw new NotEnoughMonitorDataException($diff);
+ }
+ }
+
+ /**
+ * Эмулирует обращение к атрибутам
+ *
+ * @param string $name
+ * @return null
+ * @throws Exception
+ */
+ public function __get(string $name)
+ {
+ if (empty($this->data?->$name)) {
+ return null;
+ }
+ if (in_array($name, $this->timestamps)) {
+ return new DateTime($this->data->$name);
+ }
+ return $this->data->$name;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ return (array)$this->data;
+ }
+}
diff --git a/src/Entities/MoneyTransferOperator.php b/src/Entities/MoneyTransferOperator.php
new file mode 100644
index 0000000..1ccdb1e
--- /dev/null
+++ b/src/Entities/MoneyTransferOperator.php
@@ -0,0 +1,133 @@
+setName($name);
+ $this->setInn($inn);
+ $this->setAddress($address);
+ $this->setPhones($phones);
+ }
+
+ /**
+ * Возвращает установленное наименование поставщика
+ *
+ * @return string|null
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Устанавливает наименование поставщика
+ *
+ * @param string|null $name
+ * @return $this
+ */
+ public function setName(?string $name): self
+ {
+ // критерии валидной строки не описаны ни в схеме, ни в документации
+ $this->name = trim((string)$name) ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный адрес места расчётов
+ *
+ * @return string|null
+ */
+ public function getAddress(): ?string
+ {
+ return $this->address;
+ }
+
+ /**
+ * Устанавливает адрес места расчётов
+ *
+ * @param string|null $address
+ * @return $this
+ */
+ public function setAddress(?string $address): self
+ {
+ // критерии валидной строки не описаны ни в схеме, ни в документации
+ $this->address = trim((string)$address) ?: null;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ $this->getName() && $json['name'] = $this->getName();
+ $this->getInn() && $json['inn'] = $this->getInn();
+ $this->getAddress() && $json['address'] = $this->getAddress();
+ !$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
+ return $json;
+ }
+}
diff --git a/src/Entities/PayingAgent.php b/src/Entities/PayingAgent.php
new file mode 100644
index 0000000..7620382
--- /dev/null
+++ b/src/Entities/PayingAgent.php
@@ -0,0 +1,90 @@
+setOperation($operation);
+ $this->setPhones($phones);
+ }
+
+ /**
+ * Устанавливает операцию
+ *
+ * @param string|null $operation
+ * @return $this
+ * @throws TooLongPayingAgentOperationException
+ */
+ public function setOperation(?string $operation): self
+ {
+ if (!is_null($operation)) {
+ $operation = trim($operation);
+ if (mb_strlen($operation) > Constraints::MAX_LENGTH_PAYING_AGENT_OPERATION) {
+ throw new TooLongPayingAgentOperationException($operation);
+ }
+ }
+ $this->operation = $operation ?: null;
+ return $this;
+ }
+
+ /**
+ * Вoзвращает установленную операцию
+ *
+ * @return string|null
+ */
+ public function getOperation(): ?string
+ {
+ return $this->operation;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ $this->getOperation() && $json['operation'] = $this->getOperation();
+ !$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
+ return $json;
+ }
+}
diff --git a/src/Entities/Payment.php b/src/Entities/Payment.php
new file mode 100644
index 0000000..e4e57b4
--- /dev/null
+++ b/src/Entities/Payment.php
@@ -0,0 +1,125 @@
+setType($type)->setSum($sum);
+ }
+
+ /**
+ * Возвращает установленный тип оплаты
+ *
+ * @return int
+ */
+ public function getType(): int
+ {
+ return $this->type;
+ }
+
+ /**
+ * Устанавливает тип оплаты
+ *
+ * @param int $type
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setType(int $type): self
+ {
+ PaymentTypes::isValid($type) && $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную сумму оплаты
+ *
+ * @return float
+ */
+ public function getSum(): float
+ {
+ return $this->sum;
+ }
+
+ /**
+ * Устанавливает сумму оплаты
+ *
+ * @param float $sum
+ * @return $this
+ * @throws TooHighPaymentSumException
+ * @throws NegativePaymentSumException
+ */
+ public function setSum(float $sum): self
+ {
+ $sum = round($sum, 2);
+ if ($sum > Constraints::MAX_COUNT_PAYMENT_SUM) {
+ throw new TooHighPaymentSumException($sum);
+ }
+ if ($sum < 0) {
+ throw new NegativePaymentSumException($sum);
+ }
+ $this->sum = $sum;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ #[ArrayShape(['type' => 'int', 'sum' => 'float'])]
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => $this->getType(),
+ 'sum' => $this->getSum(),
+ ];
+ }
+}
diff --git a/src/Entities/Receipt.php b/src/Entities/Receipt.php
new file mode 100644
index 0000000..67c7e59
--- /dev/null
+++ b/src/Entities/Receipt.php
@@ -0,0 +1,480 @@
+setClient($client)->setCompany($company)->setItems($items)->setPayments($payments);
+ }
+
+ /**
+ * Возвращает установленного покупателя
+ *
+ * @return Client
+ */
+ public function getClient(): Client
+ {
+ return $this->client;
+ }
+
+ /**
+ * Устанаваливает покупателя
+ *
+ * @param Client $client
+ * @return $this
+ */
+ public function setClient(Client $client): self
+ {
+ $this->client = $client;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного продавца
+ *
+ * @return Company
+ */
+ public function getCompany(): Company
+ {
+ return $this->company;
+ }
+
+ /**
+ * Устанаваливает продавца
+ *
+ * @param Company $company
+ * @return $this
+ */
+ public function setCompany(Company $company): self
+ {
+ $this->company = $company;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленного агента
+ *
+ * @return AgentInfo|null
+ */
+ public function getAgentInfo(): ?AgentInfo
+ {
+ return $this->agent_info;
+ }
+
+ /**
+ * Устанаваливает агента
+ *
+ * @param AgentInfo|null $agent_info
+ * @return $this
+ */
+ public function setAgentInfo(?AgentInfo $agent_info): self
+ {
+ $this->agent_info = $agent_info;
+ return $this;
+ }
+
+ /**
+ * Поставщика
+ *
+ * @return Supplier|null
+ */
+ public function getSupplier(): ?Supplier
+ {
+ return $this->supplier;
+ }
+
+ /**
+ * Поставщика
+ *
+ * @param Supplier|null $supplier
+ * @return $this
+ */
+ public function setSupplier(?Supplier $supplier): self
+ {
+ $this->supplier = $supplier;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную коллекцию предметов расчёта
+ *
+ * @return Items
+ */
+ public function getItems(): Items
+ {
+ return $this->items ?? new Items();
+ }
+
+ /**
+ * Устанаваливает коллекцию предметов расчёта
+ *
+ * @param Items $items
+ * @return $this
+ * @throws InvalidEntityInCollectionException
+ * @throws Exception
+ * @throws EmptyItemsException
+ */
+ public function setItems(Items $items): self
+ {
+ $items->checkCount();
+ $items->checkItemsClasses();
+ $this->items = $items;
+ $this->getItems()->each(fn($item) => $this->total += $item->getSum());
+ $this->total = round($this->total, 2);
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную коллекцию оплат
+ *
+ * @return Payments
+ */
+ public function getPayments(): Payments
+ {
+ return $this->payments;
+ }
+
+ /**
+ * Устанаваливает коллекцию оплат
+ *
+ * @param Payments $payments
+ * @return $this
+ * @throws InvalidEntityInCollectionException
+ */
+ public function setPayments(Payments $payments): self
+ {
+ $payments->checkCount();
+ $payments->checkItemsClasses();
+ $this->payments = $payments;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленную коллекцию ставок НДС
+ *
+ * @return Vats|null
+ */
+ public function getVats(): ?Vats
+ {
+ return $this->vats ?? new Vats();
+ }
+
+ /**
+ * Устанаваливает коллекцию ставок НДС
+ *
+ * @param Vats|null $vats
+ * @return $this
+ * @throws Exception
+ */
+ public function setVats(?Vats $vats): self
+ {
+ $vats->checkCount();
+ $vats->checkItemsClasses();
+ $this->vats = $vats;
+ /** @var Vat $vat */
+ $this->getVats()->each(fn($vat) => $vat->setSum($this->getTotal()));
+ return $this;
+ }
+
+ /**
+ * Возвращает полную сумму чека
+ *
+ * @return float
+ */
+ public function getTotal(): float
+ {
+ return $this->total;
+ }
+
+ /**
+ * Возвращает установленного кассира
+ *
+ * @return string|null
+ */
+ public function getCashier(): ?string
+ {
+ return $this->cashier;
+ }
+
+ /**
+ * Устанаваливает кассира
+ *
+ * @param string|null $cashier
+ * @return $this
+ * @throws TooLongCashierException
+ */
+ public function setCashier(?string $cashier): self
+ {
+ if (is_string($cashier)) {
+ $cashier = trim($cashier);
+ if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
+ throw new TooLongCashierException($cashier);
+ }
+ }
+ $this->cashier = $cashier ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный дополнительный реквизит чека
+ *
+ * @return string|null
+ */
+ public function getAddCheckProps(): ?string
+ {
+ return $this->add_check_props;
+ }
+
+ /**
+ * Устанаваливает дополнительный реквизит чека
+ *
+ * @param string|null $add_check_props
+ * @return $this
+ * @throws TooLongAddCheckPropException
+ */
+ public function setAddCheckProps(?string $add_check_props): self
+ {
+ if (is_string($add_check_props)) {
+ $add_check_props = trim($add_check_props);
+ if (mb_strlen($add_check_props) > Constraints::MAX_LENGTH_ADD_CHECK_PROP) {
+ throw new TooLongAddCheckPropException($add_check_props);
+ }
+ }
+ $this->add_check_props = $add_check_props ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный дополнительный реквизит пользователя
+ *
+ * @return AdditionalUserProps|null
+ */
+ public function getAddUserProps(): ?AdditionalUserProps
+ {
+ return $this->add_user_props;
+ }
+
+ /**
+ * Устанаваливает дополнительный реквизит пользователя
+ *
+ * @param AdditionalUserProps|null $add_user_props
+ * @return $this
+ */
+ public function setAddUserProps(?AdditionalUserProps $add_user_props): self
+ {
+ $this->add_user_props = $add_user_props;
+ return $this;
+ }
+
+ /**
+ * Регистрирует приход по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sell(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->sell($this, $external_id);
+ }
+
+ /**
+ * Регистрирует возврат прихода по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function sellRefund(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->sellRefund($this, $external_id);
+ }
+
+ /**
+ * Регистрирует расход по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buy(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->buy($this, $external_id);
+ }
+
+ /**
+ * Регистрирует возврат расхода по текущему документу
+ *
+ * @param Fiscalizer $fiscalizer Объект фискализатора
+ * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID)
+ * @return AtolResponse|null
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongPaymentAddressException
+ */
+ public function buyRefund(Fiscalizer $fiscalizer, ?string $external_id = null): ?AtolResponse
+ {
+ return $fiscalizer->buyRefund($this, $external_id);
+ }
+
+ /**
+ * Возвращает массив для кодирования в json
+ *
+ * @throws Exception
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [
+ 'client' => $this->getClient()->jsonSerialize(),
+ 'company' => $this->getCompany()->jsonSerialize(),
+ 'items' => $this->getItems()->jsonSerialize(),
+ 'total' => $this->getTotal(),
+ 'payments' => $this->getPayments()->jsonSerialize(),
+ ];
+ $this->getAgentInfo()?->jsonSerialize() && $json['agent_info'] = $this->getAgentInfo()->jsonSerialize();
+ $this->getSupplier()?->jsonSerialize() && $json['supplier_info'] = $this->getSupplier()->jsonSerialize();
+ $this->getVats()?->isNotEmpty() && $json['vats'] = $this->getVats();
+ !is_null($this->getAddCheckProps()) && $json['additional_check_props'] = $this->getAddCheckProps();
+ !is_null($this->getCashier()) && $json['cashier'] = $this->getCashier();
+ $this->getAddUserProps()?->jsonSerialize() &&
+ $json['additional_user_props'] = $this->getAddUserProps()->jsonSerialize();
+ return $json;
+ }
+}
diff --git a/src/Entities/ReceivePaymentsOperator.php b/src/Entities/ReceivePaymentsOperator.php
new file mode 100644
index 0000000..83b52a3
--- /dev/null
+++ b/src/Entities/ReceivePaymentsOperator.php
@@ -0,0 +1,47 @@
+setPhones($phones);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ !$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
+ return $json;
+ }
+}
diff --git a/src/Entities/Supplier.php b/src/Entities/Supplier.php
new file mode 100644
index 0000000..579e241
--- /dev/null
+++ b/src/Entities/Supplier.php
@@ -0,0 +1,91 @@
+setName($name);
+ !is_null($inn) && $this->setInn($inn);
+ $this->setPhones($phones);
+ }
+
+ /**
+ * Возвращает установленное наименование поставщика
+ *
+ * @return string|null
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Устанавливает наименование поставщика
+ *
+ * @param string|null $name
+ * @return Supplier
+ */
+ public function setName(?string $name): self
+ {
+ // критерии к длине строки не описаны ни в схеме, ни в документации
+ $this->name = trim($name) ?: null;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function jsonSerialize(): array
+ {
+ $json = [];
+ $this->getName() && $json['name'] = $this->getName();
+ $this->getInn() && $json['inn'] = $this->getInn();
+ !$this->getPhones()->isEmpty() && $json['phones'] = $this->getPhones()->toArray();
+ return $json;
+ }
+}
diff --git a/src/Entities/Vat.php b/src/Entities/Vat.php
new file mode 100644
index 0000000..e65e829
--- /dev/null
+++ b/src/Entities/Vat.php
@@ -0,0 +1,146 @@
+setType($type)->setSum($rubles);
+ }
+
+ /**
+ * Устанавливает тип ставки НДС
+ * Автоматически пересчитывает итоговый размер НДС от исходной суммы.
+ *
+ * @param string $type Тип ставки НДС
+ * @return $this
+ * @throws InvalidEnumValueException
+ */
+ public function setType(string $type): self
+ {
+ $type = trim($type);
+ VatTypes::isValid($type) && $this->type = $type;
+ return $this;
+ }
+
+ /**
+ * Возвращает тип ставки НДС
+ *
+ * @return string
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Возвращает исходную сумму, от которой расчитывается размер налога
+ *
+ * @return float
+ */
+ public function getSum(): float
+ {
+ return $this->sum;
+ }
+
+ /**
+ * Устанавливает исходную сумму, от которой будет расчитываться итоговый размер НДС.
+ * Автоматически пересчитывает итоговый размер НДС от исходной суммы.
+ *
+ * @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС
+ * @return $this
+ */
+ public function setSum(float $rubles): self
+ {
+ $this->sum = round($rubles, 2);
+ return $this;
+ }
+
+ /**
+ * Возвращает расчитанный итоговый размер ставки НДС в рублях
+ *
+ * @return float
+ * @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/
+ * @see https://glavkniga.ru/situations/k500734
+ * @see https://www.b-kontur.ru/nds-kalkuljator-online
+ */
+ #[Pure]
+ public function getCalculated(): float
+ {
+ return Helpers::toRub(
+ match ($this->getType()) {
+ VatTypes::VAT10 => Helpers::toKop($this->sum) * 10 / 100,
+ VatTypes::VAT18 => Helpers::toKop($this->sum) * 18 / 100,
+ VatTypes::VAT20 => Helpers::toKop($this->sum) * 20 / 100,
+ VatTypes::VAT110 => Helpers::toKop($this->sum) * 10 / 110,
+ VatTypes::VAT118 => Helpers::toKop($this->sum) * 18 / 118,
+ VatTypes::VAT120 => Helpers::toKop($this->sum) * 20 / 120,
+ default => 0,
+ }
+ );
+ }
+
+ /**
+ * Прибавляет указанную сумму к исходной
+ *
+ * @param float $rubles
+ * @return $this
+ */
+ public function addSum(float $rubles): self
+ {
+ $this->sum += $rubles;
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ #[Pure]
+ #[ArrayShape(['type' => 'string', 'sum' => 'float'])]
+ public function jsonSerialize(): array
+ {
+ return [
+ 'type' => $this->getType(),
+ 'sum' => $this->getCalculated(),
+ ];
+ }
+}
diff --git a/src/Enums/AgentTypes.php b/src/Enums/AgentTypes.php
new file mode 100644
index 0000000..8d906dc
--- /dev/null
+++ b/src/Enums/AgentTypes.php
@@ -0,0 +1,65 @@
+ffd_tags);
+ parent::__construct(
+ ($message ?: $this->message) . ($tags ? ' [Теги ФФД: ' . $tags . ']' : '')
+ );
+ }
+}
diff --git a/src/Exceptions/AuthFailedException.php b/src/Exceptions/AuthFailedException.php
new file mode 100644
index 0000000..6d2ab22
--- /dev/null
+++ b/src/Exceptions/AuthFailedException.php
@@ -0,0 +1,34 @@
+message . implode(', ', $props_diff));
+ }
+}
diff --git a/src/Exceptions/TooHighItemPriceException.php b/src/Exceptions/TooHighItemPriceException.php
new file mode 100644
index 0000000..84c6930
--- /dev/null
+++ b/src/Exceptions/TooHighItemPriceException.php
@@ -0,0 +1,37 @@
+message) . ': ' . $value . (((float)$max > 0 || (float)$this->max > 0) ?
+ ' (макс = ' . ($max ?: $this->max) . ', фактически = ' . mb_strlen($value) . ')' : '')
+ );
+ }
+}
diff --git a/src/Exceptions/TooLongItemCodeException.php b/src/Exceptions/TooLongItemCodeException.php
new file mode 100644
index 0000000..79ff94a
--- /dev/null
+++ b/src/Exceptions/TooLongItemCodeException.php
@@ -0,0 +1,37 @@
+message) . (((float)$max > 0 || (float)$this->max > 0) ?
+ ' (макс = ' . ($max ?? $this->max) . ', фактически = ' . $value . ')' : '')
+ );
+ }
+}
diff --git a/src/AtolOnline/Exceptions/AtolTooManyItemsException.php b/src/Exceptions/TooManyItemsException.php
similarity index 52%
rename from src/AtolOnline/Exceptions/AtolTooManyItemsException.php
rename to src/Exceptions/TooManyItemsException.php
index ef0c8d1..8f95a66 100644
--- a/src/AtolOnline/Exceptions/AtolTooManyItemsException.php
+++ b/src/Exceptions/TooManyItemsException.php
@@ -1,23 +1,23 @@
"string",
+ 'company_name' => "string",
+ 'inn' => "string",
+ 'payment_address' => "string",
+ 'group' => "string",
+ 'login' => "string",
+ 'password' => "string",
+ 'endpoint_ofd' => "string",
+ ])]
+ public static function FFD105(): array
+ {
+ return [
+ 'endpoint' => 'https://testonline.atol.ru/possystem/v4/',
+ 'company_name' => 'АТОЛ',
+ 'inn' => '5544332219',
+ 'payment_address' => 'https://v4.online.atol.ru',
+ 'group' => 'v4-online-atol-ru_4179',
+ 'login' => 'v4-online-atol-ru',
+ 'password' => 'iGFFuihss',
+ 'endpoint_ofd' => 'https://consumer.1-ofd-test.ru/v1',
+ ];
+ }
+
+ /**
+ * Возвращает данные для работы с тестовой средой АТОЛ Онлайн ФФД 1.2
+ *
+ * @return string[]
+ * @noinspection PhpUnused
+ */
+ #[ArrayShape([
+ 'endpoint' => "string",
+ 'company_name' => "string",
+ 'inn' => "string",
+ 'payment_address' => "string",
+ 'group' => "string",
+ 'login' => "string",
+ 'password' => "string",
+ 'endpoint_ofd' => "string",
+ ])]
+ public static function FFD12(): array
+ {
+ return [
+ 'endpoint' => 'https://testonline.atol.ru/possystem/v5/',
+ 'company_name' => 'АТОЛ',
+ 'inn' => '5544332219',
+ 'payment_address' => 'https://v5.online.atol.ru',
+ 'group' => 'v5-online-atol-ru_5179',
+ 'login' => 'v5-online-atol-ru',
+ 'password' => 'zUr0OxfI',
+ 'endpoint_ofd' => '',
+ ];
+ }
+}
diff --git a/src/Traits/HasEmail.php b/src/Traits/HasEmail.php
new file mode 100644
index 0000000..661902c
--- /dev/null
+++ b/src/Traits/HasEmail.php
@@ -0,0 +1,57 @@
+ Constraints::MAX_LENGTH_EMAIL) {
+ throw new TooLongEmailException($email);
+ } elseif (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
+ throw new InvalidEmailException($email);
+ }
+ }
+ $this->email = $email ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный email
+ *
+ * @return string|null
+ */
+ public function getEmail(): ?string
+ {
+ return $this->email;
+ }
+}
diff --git a/src/Traits/HasInn.php b/src/Traits/HasInn.php
new file mode 100644
index 0000000..8160ac4
--- /dev/null
+++ b/src/Traits/HasInn.php
@@ -0,0 +1,53 @@
+inn = $inn ?: null;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленный ИНН
+ *
+ * @return string|null
+ */
+ public function getInn(): ?string
+ {
+ return $this->inn;
+ }
+}
diff --git a/src/Traits/HasPhones.php b/src/Traits/HasPhones.php
new file mode 100644
index 0000000..a6eb5e9
--- /dev/null
+++ b/src/Traits/HasPhones.php
@@ -0,0 +1,57 @@
+each(function ($phone) {
+ $phone = preg_replace('/[^\d]/', '', trim($phone));
+ if (preg_match(Constraints::PATTERN_PHONE, $phone) != 1) {
+ throw new InvalidPhoneException($phone);
+ }
+ });
+ }
+ $this->phones = empty($phones) ? collect() : $phones;
+ return $this;
+ }
+
+ /**
+ * Возвращает установленные номера телефонов
+ *
+ * @return Collection
+ */
+ public function getPhones(): Collection
+ {
+ return $this->phones;
+ }
+}
diff --git a/src/helpers.php b/src/helpers.php
deleted file mode 100644
index c4f0900..0000000
--- a/src/helpers.php
+++ /dev/null
@@ -1,16 +0,0 @@
-assertIsObject($fisc);
+ $this->assertIsSameClass(Fiscalizer::class, $fisc);
+ $this->assertExtendsClasses([AtolClient::class], $fisc);
+ }
+
+ /**
+ * Тестирует установку и возврат группы ККТ
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer
+ * @covers \AtolOnline\Api\Fiscalizer::getGroup
+ * @covers \AtolOnline\Api\Fiscalizer::setGroup
+ */
+ public function testGroup(): void
+ {
+ // test mode
+ $this->assertEquals(
+ TestEnvParams::FFD105()['group'],
+ (new Fiscalizer(group: 'group'))->getGroup()
+ );
+ // prod mode
+ $this->assertEquals('group', (new Fiscalizer(false, group: 'group'))->getGroup());
+ $this->assertNull((new Fiscalizer(false))->getGroup());
+ }
+
+ /**
+ * Тестирует выброс исключения при попытке передать пустую группу ККТ в конструктор
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer
+ * @covers \AtolOnline\Api\Fiscalizer::setGroup
+ * @covers \AtolOnline\Exceptions\EmptyGroupException
+ */
+ public function testEmptyGroupException(): void
+ {
+ $this->expectException(EmptyGroupException::class);
+ new Fiscalizer(group: "\n\r \0\t");
+ }
+
+ /**
+ * Тестирует выброс исключения при попытке установить слишком длинный адрес колбека
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer::setCallbackUrl
+ * @covers \AtolOnline\Exceptions\TooLongCallbackUrlException
+ * @throws InvalidCallbackUrlException
+ * @throws TooLongCallbackUrlException
+ */
+ public function testTooLongCallbackUrlException(): void
+ {
+ $this->expectException(TooLongCallbackUrlException::class);
+ (new Fiscalizer())->setCallbackUrl(Helpers::randomStr(Constraints::MAX_LENGTH_CALLBACK_URL + 1));
+ }
+
+ /**
+ * Тестирует выброс исключения при попытке установить слишком длинный адрес колбека
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer::setCallbackUrl
+ * @covers \AtolOnline\Exceptions\InvalidCallbackUrlException
+ * @throws InvalidCallbackUrlException
+ * @throws TooLongCallbackUrlException
+ */
+ public function testInvalidCallbackUrlException(): void
+ {
+ $this->expectException(InvalidCallbackUrlException::class);
+ (new Fiscalizer())->setCallbackUrl(Helpers::randomStr());
+ }
+
+ /**
+ * Тестирует обнуление адреса колбека
+ *
+ * @param mixed $param
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer::setCallbackUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getCallbackUrl
+ * @dataProvider providerNullableStrings
+ * @throws InvalidCallbackUrlException
+ * @throws TooLongCallbackUrlException
+ */
+ public function testNullableCallbackUrl(mixed $param): void
+ {
+ $this->assertNull((new Fiscalizer())->setCallbackUrl($param)->getCallbackUrl());
+ }
+
+ /**
+ * Тестирует регистрацию документа прихода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::sell
+ * @covers \AtolOnline\Api\Fiscalizer::sell
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooLongPaymentAddressException
+ * @throws TooManyException
+ * @throws GuzzleException
+ */
+ public function testSell(): void
+ {
+ $fisc_result = $this->newReceipt()->sell(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует регистрацию документа возврата прихода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::sellRefund
+ * @covers \AtolOnline\Api\Fiscalizer::sellRefund
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooLongPaymentAddressException
+ * @throws TooManyException
+ * @throws GuzzleException
+ */
+ public function testSellRefund(): void
+ {
+ $fisc_result = $this->newReceipt()->sellRefund(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует регистрацию документа возврата прихода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Correction::sellCorrect
+ * @covers \AtolOnline\Api\Fiscalizer::sellCorrect
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongPaymentAddressException
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ */
+ public function testSellCorrect(): void
+ {
+ $fisc_result = $this->newCorrection()->sellCorrect(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ //self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует регистрацию документа расхода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::buy
+ * @covers \AtolOnline\Api\Fiscalizer::buy
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooLongPaymentAddressException
+ * @throws TooManyException
+ * @throws GuzzleException
+ */
+ public function testBuy(): void
+ {
+ $fisc_result = $this->newReceipt()->buy(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ //self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует регистрацию документа возврата расхода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::buyRefund
+ * @covers \AtolOnline\Api\Fiscalizer::buyRefund
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooLongPaymentAddressException
+ * @throws TooManyException
+ * @throws GuzzleException
+ */
+ public function testBuyRefund(): void
+ {
+ $fisc_result = $this->newReceipt()->buyRefund(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ //self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует регистрацию документа возврата прихода
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Correction::buyCorrect
+ * @covers \AtolOnline\Api\Fiscalizer::buyCorrect
+ * @covers \AtolOnline\Api\Fiscalizer::getFullUrl
+ * @covers \AtolOnline\Api\Fiscalizer::getAuthEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::getMainEndpoint
+ * @covers \AtolOnline\Api\Fiscalizer::registerDocument
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongPaymentAddressException
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ */
+ public function testBuyCorrect(): void
+ {
+ $fisc_result = $this->newCorrection()->buyCorrect(new Fiscalizer());
+ $this->assertTrue($fisc_result->isSuccessful());
+ $this->assertEquals('wait', $fisc_result->getContent()->status);
+ //self::$registered_uuids[] = $fisc_result->getContent()->uuid;
+ }
+
+ /**
+ * Тестирует разовое получение статуса фискализации документа
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer::getDocumentStatus
+ * @depends testSell
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidUuidException
+ */
+ public function testGetDocumentStatus(): void
+ {
+ $fisc_status = (new Fiscalizer())->getDocumentStatus(array_shift(self::$registered_uuids));
+ $this->assertTrue($fisc_status->isSuccessful());
+ $this->assertTrue(in_array($fisc_status->getContent()->status, ['wait', 'done']));
+ }
+
+ /**
+ * Тестирует опрос API на получение статуса фискализации документа
+ *
+ * @return void
+ * @covers \AtolOnline\Api\Fiscalizer::pollDocumentStatus
+ * @depends testSellRefund
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws InvalidUuidException
+ */
+ public function testPollDocumentStatus(): void
+ {
+ $fisc_status = (new Fiscalizer())->pollDocumentStatus(array_shift(self::$registered_uuids));
+ $this->assertTrue($fisc_status->isSuccessful());
+ $this->assertEquals('done', $fisc_status->getContent()->status);
+ }
+}
diff --git a/tests/AtolOnline/Tests/Api/MonitorTest.php b/tests/AtolOnline/Tests/Api/MonitorTest.php
new file mode 100644
index 0000000..bebe25c
--- /dev/null
+++ b/tests/AtolOnline/Tests/Api/MonitorTest.php
@@ -0,0 +1,347 @@
+setLogin(TestEnvParams::FFD105()['login'])
+ ->setPassword(TestEnvParams::FFD105()['password']);
+ }
+
+ /**
+ * Тестирует успешное создание объекта монитора без аргументов конструктора
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ */
+ public function testConstructorWithoutArgs(): void
+ {
+ $client = new Monitor();
+ $this->assertIsObject($client);
+ $this->assertIsSameClass(Monitor::class, $client);
+ $this->assertExtendsClasses([AtolClient::class], $client);
+ }
+
+ /**
+ * Тестирует успешное создание объекта монитора с аргументами конструктора
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::setLogin
+ * @covers \AtolOnline\Api\Monitor::getLogin
+ * @covers \AtolOnline\Api\Monitor::setPassword
+ * @covers \AtolOnline\Api\Monitor::getPassword
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $client = new Monitor(false, 'login', 'password', []);
+ $this->assertIsObject($client);
+ $this->assertIsSameClass($client, Monitor::class);
+ $this->assertExtendsClasses([AtolClient::class], $client);
+ }
+ //
+
+ /**
+ * Тестирует установку и возврат логина
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::getLogin
+ * @covers \AtolOnline\Api\Monitor::setLogin
+ * @throws EmptyLoginException
+ * @throws TooLongLoginException
+ */
+ public function testLogin(): void
+ {
+ $client = new Monitor(false, login: 'login');
+ $this->assertEquals('login', $client->getLogin());
+
+ $client = new Monitor();
+ $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin());
+
+ $client->setLogin('login');
+ $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin());
+ }
+
+ /**
+ * Тестирует исключение при попытке передать пустой логин в конструктор
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::setLogin
+ * @covers \AtolOnline\Exceptions\EmptyLoginException
+ */
+ public function testEmptyLoginException(): void
+ {
+ $this->expectException(EmptyLoginException::class);
+ new Monitor(login: '');
+ }
+
+ /**
+ * Тестирует исключение при попытке передать слишком длинный логин в конструктор
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::setLogin
+ * @covers \AtolOnline\Exceptions\TooLongLoginException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testTooLongLoginException(): void
+ {
+ $this->expectException(TooLongLoginException::class);
+ new Monitor(login: Helpers::randomStr(101));
+ }
+
+ /**
+ * Тестирует установку и возврат пароля
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::getPassword
+ * @covers \AtolOnline\Api\Monitor::setPassword
+ * @throws EmptyPasswordException
+ * @throws TooLongPasswordException
+ */
+ public function testPassword(): void
+ {
+ $client = new Monitor(false, password: 'password');
+ $this->assertEquals('password', $client->getPassword());
+
+ $client = new Monitor();
+ $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword());
+
+ $client->setPassword('password');
+ $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword());
+ }
+
+ /**
+ * Тестирует исключение при попытке передать пустой пароль в конструктор
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::setPassword
+ * @covers \AtolOnline\Exceptions\EmptyPasswordException
+ */
+ public function testEmptyPasswordException(): void
+ {
+ $this->expectException(EmptyPasswordException::class);
+ new Monitor(password: '');
+ }
+
+ /**
+ * Тестирует исключение при попытке передать слишком длинный пароль в конструктор
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::setPassword
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testConstructorWithLongPassword(): void
+ {
+ $this->expectException(TooLongPasswordException::class);
+ new Monitor(password: Helpers::randomStr(101));
+ }
+
+ /**
+ * Тестирует установку тестового режима
+ *
+ * @covers \AtolOnline\Api\Monitor::__construct
+ * @covers \AtolOnline\Api\Monitor::isTestMode
+ * @covers \AtolOnline\Api\Monitor::setTestMode
+ */
+ public function testTestMode(): void
+ {
+ $client = new Monitor();
+ $this->assertTrue($client->isTestMode());
+
+ $client = new Monitor(true);
+ $this->assertTrue($client->isTestMode());
+
+ $client = new Monitor(false);
+ $this->assertFalse($client->isTestMode());
+
+ $client = (new Monitor())->setTestMode();
+ $this->assertTrue($client->isTestMode());
+
+ $client = (new Monitor())->setTestMode(true);
+ $this->assertTrue($client->isTestMode());
+
+ $client = (new Monitor())->setTestMode(false);
+ $this->assertFalse($client->isTestMode());
+ }
+
+ /**
+ * Тестирует авторизацию
+ *
+ * @covers \AtolOnline\Api\AtolClient::getHeaders
+ * @covers \AtolOnline\Api\Monitor::sendRequest
+ * @covers \AtolOnline\Api\Monitor::getAuthEndpoint
+ * @covers \AtolOnline\Api\Monitor::doAuth
+ * @covers \AtolOnline\Api\Monitor::auth
+ * @covers \AtolOnline\Exceptions\AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ * @throws AuthFailedException
+ * @throws GuzzleException
+ */
+ public function testAuth(): void
+ {
+ $this->skipIfMonitoringIsOffline();
+ $result = $this->newTestClient()->auth();
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Тестирует возврат токена после авторизации
+ *
+ * @depends testAuth
+ * @covers \AtolOnline\Api\Monitor::setToken
+ * @covers \AtolOnline\Api\Monitor::getToken
+ * @covers \AtolOnline\Exceptions\AuthFailedException
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testGetToken(): void
+ {
+ $client = new Monitor();
+ $this->assertNull($client->getToken());
+
+ $this->skipIfMonitoringIsOffline();
+ $client = $this->newTestClient();
+ $client->auth();
+ $this->assertIsString($client->getToken());
+ }
+
+ /**
+ * Тестирует возврат объекта последнего ответа от API
+ *
+ * @depends testAuth
+ * @covers \AtolOnline\Api\Monitor::getLastResponse
+ * @covers \AtolOnline\Exceptions\AuthFailedException
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testGetResponse(): void
+ {
+ $this->skipIfMonitoringIsOffline();
+ $client = $this->newTestClient();
+ $client->auth();
+ $this->assertIsSameClass(AtolResponse::class, $client->getLastResponse());
+ }
+
+ /**
+ * [Мониторинг] Тестирует получение данных о всех ККТ
+ *
+ * @depends testAuth
+ * @covers \AtolOnline\Api\Monitor::getMainEndpoint
+ * @covers \AtolOnline\Api\AtolClient::getUrlToMethod
+ * @covers \AtolOnline\Api\Monitor::fetchAll
+ * @covers \AtolOnline\Api\Monitor::getAll
+ * @covers \AtolOnline\Exceptions\AuthFailedException
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ */
+ public function testMonitorGetAll(): void
+ {
+ $this->skipIfMonitoringIsOffline();
+ $client = $this->newTestClient();
+ $client->auth();
+ $kkts = $client->getAll();
+ $sss = $kkts->where('deviceNumber', 'KKT014034');
+ $this->assertNotEmpty($client->getLastResponse()->getContent());
+ $this->assertIsCollection($kkts);
+ $this->assertTrue($kkts->count() > 0);
+ $this->assertIsSameClass(Kkt::class, $kkts->random());
+ }
+
+ /**
+ * [Мониторинг] Тестирует получение данных о конкретной ККТ
+ *
+ * @depends testAuth
+ * @covers \AtolOnline\Api\Monitor::getMainEndpoint
+ * @covers \AtolOnline\Api\AtolClient::getUrlToMethod
+ * @covers \AtolOnline\Api\Monitor::fetchOne
+ * @covers \AtolOnline\Api\Monitor::getOne
+ * @covers \AtolOnline\Entities\Kkt::__construct
+ * @covers \AtolOnline\Entities\Kkt::__get
+ * @covers \AtolOnline\Entities\Kkt::jsonSerialize
+ * @covers \AtolOnline\Entities\Kkt::__toString
+ * @throws AuthFailedException
+ * @throws EmptyLoginException
+ * @throws EmptyPasswordException
+ * @throws GuzzleException
+ * @throws TooLongLoginException
+ * @throws TooLongPasswordException
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ * @throws Exception
+ */
+ public function testMonitorGetOne(): void
+ {
+ $this->skipIfMonitoringIsOffline();
+ $client = $this->newTestClient();
+ $client->auth();
+ $serial_number = $client->getAll()->first()->serialNumber;
+ $kkt = $client->getOne($serial_number);
+ $this->assertNotEmpty($client->getLastResponse());
+ $this->assertIsSameClass(Kkt::class, $kkt);
+ $this->assertIsAtolable($kkt);
+ $this->assertNotNull($kkt->serialNumber);
+ $this->assertEquals($serial_number, $kkt->serialNumber);
+ }
+}
diff --git a/tests/AtolOnline/Tests/BasicTestCase.php b/tests/AtolOnline/Tests/BasicTestCase.php
new file mode 100644
index 0000000..8f576d6
--- /dev/null
+++ b/tests/AtolOnline/Tests/BasicTestCase.php
@@ -0,0 +1,451 @@
+ false,
+ 'timeout' => 3,
+ ]))->request('GET', $url);
+ } catch (GuzzleException) {
+ return false;
+ }
+ return $result->getStatusCode() === $code;
+ }
+
+ /**
+ * Проверяет доступность API мониторинга
+ *
+ * @return bool
+ */
+ protected function isMonitoringOnline(): bool
+ {
+ return $this->ping('https://testonline.atol.ru/api/auth/v1/gettoken', 400);
+ }
+
+ /**
+ * Пропускает текущий тест если API мониторинга недоступен
+ */
+ protected function skipIfMonitoringIsOffline(): void
+ {
+ if (!$this->isMonitoringOnline()) {
+ $this->markTestSkipped($this->getName() . ': Monitoring API is inaccessible. Skipping test.');
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Дополнительные ассерты
+ //------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Тестирует является ли объект приводимым к json-строке согласно схеме АТОЛ Онлайн
+ *
+ * @param Entity|EntityCollection $entity
+ * @param array|null $json_structure
+ * @covers \AtolOnline\Entities\Entity::__toString
+ * @covers \AtolOnline\Entities\Entity::toArray
+ * @covers \AtolOnline\Entities\Entity::jsonSerialize
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @throws Exception
+ */
+ public function assertIsAtolable(Entity|EntityCollection $entity, ?array $json_structure = null): void
+ {
+ $this->assertIsArray($entity->jsonSerialize());
+ $this->assertIsArray($entity->toArray());
+ $this->assertEquals($entity->jsonSerialize(), $entity->toArray());
+ $this->assertIsString((string)$entity);
+ $this->assertJson((string)$entity);
+ if (!is_null($json_structure)) {
+ $this->assertEquals(json_encode($json_structure), (string)$entity);
+ }
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Ассерты проверки наследования
+ //------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Тестирует идентичность двух классов
+ *
+ * @param object|string $expected Ожидаемый класс
+ * @param object|string $actual Фактический класс
+ */
+ public function assertIsSameClass(object|string $expected, object|string $actual): void
+ {
+ $this->assertTrue($this->checkisSameClass($expected, $actual));
+ }
+
+ /**
+ * Проверяет идентичность двух классов
+ *
+ * @param object|string $class1
+ * @param object|string $class2
+ * @return bool
+ */
+ private function checkisSameClass(object|string $class1, object|string $class2): bool
+ {
+ return (is_object($class1) ? $class1::class : $class1)
+ === (is_object($class2) ? $class2::class : $class2);
+ }
+
+ /**
+ * Тестирует наследование класса (объекта) от указанных классов
+ *
+ * @param array $expected Массив ожидаемых имён классов-родителей
+ * @param object|string $actual Объект или имя класса для проверки
+ */
+ public function assertExtendsClasses(array $expected, object|string $actual): void
+ {
+ $this->assertTrue($this->checkExtendsClasses($expected, $actual));
+ }
+
+ /**
+ * Проверяет наследование класса (объекта) от указанных классов
+ *
+ * @param string[] $parents Имена классов-родителей
+ * @param object|string $class Объект или имя класса для проверки
+ */
+ private function checkExtendsClasses(array $parents, object|string $class): bool
+ {
+ return !empty(array_intersect($parents, is_object($class) ? class_parents($class) : [$class]));
+ }
+
+ /**
+ * Тестирует имплементацию классом (объектом) указанных интерфейсов
+ *
+ * @param string[] $expected Массив ожидаемых имён интерфейсов
+ * @param object|string $actual Объект или имя класса для проверки
+ */
+ public function assertImplementsInterfaces(array $expected, object|string $actual): void
+ {
+ $this->assertTrue($this->checkImplementsInterfaces($expected, $actual));
+ }
+
+ /**
+ * Проверяет имплементацию классом (объектом) указанных интерфейсов
+ *
+ * @param string[] $interfaces Имена классов-интерфейсов
+ * @param object|string $class Объект или имя класса для проверки
+ * @see https://www.php.net/manual/ru/function.class-implements.php
+ */
+ private function checkImplementsInterfaces(array $interfaces, object|string $class): bool
+ {
+ return !empty(array_intersect($interfaces, is_object($class) ? class_implements($class) : [$class]));
+ }
+
+ /**
+ * Тестирует использование классом (объектом) указанных трейтов
+ *
+ * @param string[] $expected Массив ожидаемых имён трейтов
+ * @param object|string $actual Объект или имя класса для проверки
+ */
+ public function assertUsesTraits(array $expected, object|string $actual): void
+ {
+ $this->assertTrue($this->checkUsesTraits($expected, $actual));
+ }
+
+ /**
+ * Проверяет использование классом (объектом) указанных трейтов (исключает родителей)
+ *
+ * @param string[] $traits Массив ожидаемых имён трейтов
+ * @param object|string $class Объект или имя класса для проверки
+ * @return bool
+ * @see https://www.php.net/manual/ru/function.class-uses.php#110752
+ */
+ private function checkUsesTraits(array $traits, object|string $class): bool
+ {
+ $found_traits = [];
+ $check_class = is_object($class) ? $class::class : $class;
+ do {
+ $found_traits = array_merge(class_uses($check_class, true), $found_traits);
+ } while ($check_class = get_parent_class($check_class));
+ foreach ($found_traits as $trait => $same) {
+ $found_traits = array_merge(class_uses($trait, true), $found_traits);
+ }
+ return !empty(array_intersect(array_unique($found_traits), $traits));
+ }
+
+ /**
+ * Тестирует, является ли объект коллекцией
+ *
+ * @param mixed $value
+ */
+ public function assertIsCollection(mixed $value): void
+ {
+ $this->assertIsObject($value);
+ $this->assertIsIterable($value);
+ $this->assertTrue(
+ $this->checkisSameClass(Collection::class, $value) ||
+ $this->checkExtendsClasses([Collection::class], $value)
+ );
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Провайдеры данных для прогона тестов
+ //------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Провайдер строк, которые приводятся к null
+ *
+ * @return array
+ */
+ public function providerNullableStrings(): array
+ {
+ return [
+ [''],
+ [' '],
+ [null],
+ ["\n\r\t"],
+ ];
+ }
+
+ /**
+ * Провайдер валидных телефонов
+ *
+ * @return array>
+ */
+ public function providerValidPhones(): array
+ {
+ return [
+ ['+79991234567', '+79991234567'],
+ ['79991234567', '+79991234567'],
+ ['89991234567', '+89991234567'],
+ ['+7 999 123 45 67', '+79991234567'],
+ ['+7 (999) 123-45-67', '+79991234567'],
+ ["+7 %(?9:9\"9')abc\r123\n45\t67\0", '+79991234567'],
+ ];
+ }
+
+ /**
+ * Провайдер телефонов, которые приводятся к null
+ *
+ * @return array>
+ */
+ public function providerNullablePhones(): array
+ {
+ return array_merge(
+ $this->providerNullableStrings(),
+ [
+ [Helpers::randomStr(10, false)],
+ ["asdfgvs \n\rtt\t*/(*&%^*$%"],
+ ]
+ );
+ }
+
+ /**
+ * Провайдер валидных email-ов
+ *
+ * @return array>
+ */
+ public function providerValidEmails(): array
+ {
+ return [
+ ['abc@mail.com'],
+ ['abc-d@mail.com'],
+ ['abc.def@mail.com'],
+ ['abc.def@mail.org'],
+ ['abc.def@mail-archive.com'],
+ ];
+ }
+
+ /**
+ * Провайдер невалидных email-ов
+ *
+ * @return array>
+ */
+ public function providerInvalidEmails(): array
+ {
+ return [
+ ['@example'],
+ [Helpers::randomStr(15)],
+ ['@example.com'],
+ ['abc.def@mail'],
+ ['.abc@mail.com'],
+ ['example@example'],
+ ['abc..def@mail.com'],
+ ['abc.def@mail..com'],
+ ['abc.def@mail#archive.com'],
+ ];
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Генераторы тестовых объектов
+ //------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Генерирует массив тестовых объектов предметов расчёта
+ *
+ * @param int $count
+ * @return Item[]
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ protected function generateItemObjects(int $count = 1): array
+ {
+ $result = [];
+ for ($i = 0; $i < abs($count); ++$i) {
+ $result[] = new Item(Helpers::randomStr(), random_int(1, 100), random_int(1, 10));
+ }
+ return $result;
+ }
+
+ /**
+ * Генерирует массив тестовых объектов оплаты
+ *
+ * @param int $count
+ * @return Payment[]
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws Exception
+ */
+ protected function generatePaymentObjects(int $count = 1): array
+ {
+ $types = PaymentTypes::toArray();
+ $result = [];
+ for ($i = 0; $i < abs($count); ++$i) {
+ $result[] = new Payment(
+ array_values($types)[random_int(min($types), max($types))],
+ random_int(1, 100) * 2 / 3
+ );
+ }
+ return $result;
+ }
+
+ /**
+ * Генерирует массив тестовых объектов ставок НДС
+ *
+ * @param int $count
+ * @return Vat[]
+ * @throws InvalidEnumValueException
+ * @throws Exception
+ */
+ protected function generateVatObjects(int $count = 1): array
+ {
+ $types = VatTypes::toArray();
+ $result = [];
+ for ($i = 0; $i < abs($count); ++$i) {
+ $result[] = new Vat(
+ array_values($types)[random_int(0, count($types) - 1)],
+ random_int(1, 100) * 2 / 3
+ );
+ }
+ return $result;
+ }
+
+ /**
+ * Возвращает валидный тестовый объект чека прихода
+ *
+ * @return Receipt
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ protected function newReceipt(): Receipt
+ {
+ return new Receipt(
+ new Client('John Doe', 'john@example.com', '+79501234567', '1234567890'),
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new Items($this->generateItemObjects(2)),
+ new Payments($this->generatePaymentObjects())
+ );
+ }
+
+ /**
+ * Возвращает валидный тестовый объект чека
+ *
+ * @return Correction
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ */
+ protected function newCorrection(): Correction
+ {
+ return new Correction(
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', Helpers::randomStr()),
+ new Payments($this->generatePaymentObjects(2)),
+ new Vats($this->generateVatObjects(2)),
+ );
+ }
+}
diff --git a/tests/AtolOnline/Tests/Collections/ItemsTest.php b/tests/AtolOnline/Tests/Collections/ItemsTest.php
new file mode 100644
index 0000000..6cc9ace
--- /dev/null
+++ b/tests/AtolOnline/Tests/Collections/ItemsTest.php
@@ -0,0 +1,69 @@
+expectException(TooManyItemsException::class);
+ (new Items($this->generateItemObjects(Constraints::MAX_COUNT_DOC_ITEMS + 1)))->jsonSerialize();
+ }
+
+ /**
+ * Тестирует выброс исключения при установке нулевого количества предметов расчёта
+ *
+ * @covers \AtolOnline\Collections\EntityCollection
+ * @covers \AtolOnline\Collections\EntityCollection::checkCount
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @covers \AtolOnline\Exceptions\EmptyItemsException
+ * @throws InvalidEntityInCollectionException
+ */
+ public function testEmptyItemsException()
+ {
+ $this->expectException(EmptyItemsException::class);
+ (new Items([]))->jsonSerialize();
+ }
+}
diff --git a/tests/AtolOnline/Tests/Collections/PaymentsTest.php b/tests/AtolOnline/Tests/Collections/PaymentsTest.php
new file mode 100644
index 0000000..dd1a14e
--- /dev/null
+++ b/tests/AtolOnline/Tests/Collections/PaymentsTest.php
@@ -0,0 +1,59 @@
+expectException(TooManyPaymentsException::class);
+ (new Payments($this->generatePaymentObjects(Constraints::MAX_COUNT_DOC_PAYMENTS + 1)))->jsonSerialize();
+ }
+
+ /**
+ * Тестирует выброс исключения при установке нулевого количества оплат
+ *
+ * @covers \AtolOnline\Collections\EntityCollection
+ * @covers \AtolOnline\Collections\EntityCollection::checkCount
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @covers \AtolOnline\Exceptions\EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ */
+ public function testEmptyPaymentsException()
+ {
+ $this->expectException(EmptyPaymentsException::class);
+ (new Payments([]))->jsonSerialize();
+ }
+}
diff --git a/tests/AtolOnline/Tests/Collections/VatsTest.php b/tests/AtolOnline/Tests/Collections/VatsTest.php
new file mode 100644
index 0000000..cafadc6
--- /dev/null
+++ b/tests/AtolOnline/Tests/Collections/VatsTest.php
@@ -0,0 +1,121 @@
+generateVatObjects(3));
+ $this->assertIsCollection($vats);
+ $this->assertEquals(3, $vats->count());
+ $this->assertIsAtolable($vats);
+ }
+
+ /**
+ * Тестирует выброс исключения при установке нулевого количества ставок
+ *
+ * @covers \AtolOnline\Collections\EntityCollection
+ * @covers \AtolOnline\Collections\EntityCollection::checkCount
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @covers \AtolOnline\Exceptions\EmptyVatsException
+ * @throws InvalidEntityInCollectionException
+ */
+ public function testEmptyVatsException()
+ {
+ $this->expectException(EmptyVatsException::class);
+ (new Vats([]))->jsonSerialize();
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком большого количества ставок
+ *
+ * @covers \AtolOnline\Collections\EntityCollection
+ * @covers \AtolOnline\Collections\EntityCollection::checkCount
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @covers \AtolOnline\Exceptions\TooManyVatsException
+ * @throws InvalidEnumValueException
+ * @throws InvalidEntityInCollectionException
+ */
+ public function testTooManyVatsException()
+ {
+ $this->expectException(TooManyVatsException::class);
+ (new Vats($this->generateVatObjects(Constraints::MAX_COUNT_DOC_VATS + 1)))->jsonSerialize();
+ }
+
+ /**
+ * Тестирует выброс исключения при наличии скаляров в коллекции
+ *
+ * @covers \AtolOnline\Collections\EntityCollection
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemClass
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @covers \AtolOnline\Exceptions\InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws Exception
+ */
+ public function testInvalidCollectionItemExceptionScalar(): void
+ {
+ $this->expectException(InvalidEntityInCollectionException::class);
+ $this->expectExceptionMessage("(string)'bad element'");
+ (new Vats($this->generateVatObjects(2)))
+ ->merge('bad element')
+ ->jsonSerialize();
+ }
+
+ /**
+ * Тестирует выброс исключения при наличии объектов не тех классов в коллекции
+ *
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemClass
+ * @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
+ * @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws Exception
+ */
+ public function testInvalidCollectionItemExceptionObject(): void
+ {
+ $this->expectException(InvalidEntityInCollectionException::class);
+ $this->expectExceptionMessage(Payment::class);
+ (new Vats($this->generateVatObjects()))
+ ->merge([new Payment(PaymentTypes::PREPAID, 1)])
+ ->jsonSerialize();
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/AdditionalUserPropsTest.php b/tests/AtolOnline/Tests/Entities/AdditionalUserPropsTest.php
new file mode 100644
index 0000000..8ee0f58
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/AdditionalUserPropsTest.php
@@ -0,0 +1,104 @@
+assertIsAtolable(
+ new AdditionalUserProps('name', 'value'),
+ [
+ 'name' => 'name',
+ 'value' => 'value',
+ ]
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при слишком длинном наименовании
+ *
+ * @covers \AtolOnline\Entities\AdditionalUserProps::setName
+ * @covers \AtolOnline\Exceptions\TooLongAddUserPropNameException
+ * @throws EmptyAddUserPropNameException
+ * @throws EmptyAddUserPropValueException
+ * @throws TooLongAddUserPropValueException
+ */
+ public function testTooLongAddCheckPropNameException(): void
+ {
+ $this->expectException(TooLongAddUserPropNameException::class);
+ new AdditionalUserProps(Helpers::randomStr(Constraints::MAX_LENGTH_ADD_USER_PROP_NAME + 1), 'value');
+ }
+
+ /**
+ * Тестирует выброс исключения при пустом наименовании
+ *
+ * @covers \AtolOnline\Entities\AdditionalUserProps::setName
+ * @covers \AtolOnline\Exceptions\EmptyAddUserPropNameException
+ */
+ public function testEmptyAddCheckPropNameException(): void
+ {
+ $this->expectException(EmptyAddUserPropNameException::class);
+ new AdditionalUserProps('', 'value');
+ }
+
+ /**
+ * Тестирует выброс исключения при слишком длинном значении
+ *
+ * @covers \AtolOnline\Entities\AdditionalUserProps::setValue
+ * @covers \AtolOnline\Exceptions\TooLongAddUserPropValueException
+ * @throws EmptyAddUserPropNameException
+ * @throws EmptyAddUserPropValueException
+ * @throws TooLongAddUserPropValueException
+ * @throws TooLongAddUserPropNameException
+ */
+ public function testTooLongAddCheckPropValueException(): void
+ {
+ $this->expectException(TooLongAddUserPropValueException::class);
+ new AdditionalUserProps('name', Helpers::randomStr(Constraints::MAX_LENGTH_ADD_USER_PROP_VALUE + 1));
+ }
+
+ /**
+ * Тестирует выброс исключения при пустом значении
+ *
+ * @covers \AtolOnline\Entities\AdditionalUserProps::setValue
+ * @covers \AtolOnline\Exceptions\EmptyAddUserPropValueException
+ */
+ public function testEmptyAddCheckPropValueException(): void
+ {
+ $this->expectException(EmptyAddUserPropValueException::class);
+ new AdditionalUserProps('name', '');
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/AgentInfoTest.php b/tests/AtolOnline/Tests/Entities/AgentInfoTest.php
new file mode 100644
index 0000000..745890e
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/AgentInfoTest.php
@@ -0,0 +1,120 @@
+assertIsAtolable(new AgentInfo(), []);
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и корректное приведение к json
+ *
+ * @covers \AtolOnline\Entities\AgentInfo
+ * @covers \AtolOnline\Entities\AgentInfo::jsonSerialize
+ * @covers \AtolOnline\Entities\AgentInfo::setType
+ * @covers \AtolOnline\Entities\AgentInfo::getType
+ * @covers \AtolOnline\Entities\AgentInfo::setPayingAgent
+ * @covers \AtolOnline\Entities\AgentInfo::getPayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::jsonSerialize
+ * @covers \AtolOnline\Entities\AgentInfo::setMoneyTransferOperator
+ * @covers \AtolOnline\Entities\AgentInfo::getMoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::jsonSerialize
+ * @covers \AtolOnline\Entities\AgentInfo::setReceivePaymentsOperator
+ * @covers \AtolOnline\Entities\AgentInfo::getReceivePaymentsOperator
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::jsonSerialize
+ * @throws InvalidPhoneException
+ * @throws TooLongPayingAgentOperationException
+ * @throws InvalidInnLengthException
+ * @throws InvalidEnumValueException
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $this->assertIsAtolable(new AgentInfo(null), []);
+ $this->assertIsAtolable(new AgentInfo(AgentTypes::ANOTHER), ['type' => AgentTypes::ANOTHER]);
+ $this->assertIsAtolable(new AgentInfo(pagent: new PayingAgent()), []);
+ $this->assertIsAtolable(new AgentInfo(mt_operator: new MoneyTransferOperator()), []);
+ $this->assertIsAtolable(new AgentInfo(rp_operator: new ReceivePaymentsOperator()), []);
+
+ $this->assertIsAtolable(new AgentInfo(
+ AgentTypes::ANOTHER,
+ new PayingAgent(),
+ new ReceivePaymentsOperator(),
+ new MoneyTransferOperator(),
+ ), ['type' => AgentTypes::ANOTHER]);
+
+ $this->assertIsAtolable(new AgentInfo(
+ AgentTypes::ANOTHER,
+ new PayingAgent('test', ['+79518888888']),
+ new ReceivePaymentsOperator(['+79519999999']),
+ new MoneyTransferOperator('MTO Name', '9876543210', 'London', ['+79517777777']),
+ ), [
+ 'type' => AgentTypes::ANOTHER,
+ 'paying_agent' => [
+ 'operation' => 'test',
+ 'phones' => [
+ '+79518888888',
+ ],
+ ],
+ 'receive_payments_operator' => [
+ 'phones' => [
+ '+79519999999',
+ ],
+ ],
+ 'money_transfer_operator' => [
+ 'name' => 'MTO Name',
+ 'inn' => '9876543210',
+ 'address' => 'London',
+ 'phones' => [
+ "+79517777777",
+ ],
+ ],
+ ]);
+ }
+
+ /**
+ * Тестирует исключение при некорректном типе
+ *
+ * @covers \AtolOnline\Entities\AgentInfo
+ * @covers \AtolOnline\Enums\AgentTypes::isValid
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ */
+ public function testInvalidEnumValueException(): void
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ new AgentInfo('qwerty');
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/ClientTest.php b/tests/AtolOnline/Tests/Entities/ClientTest.php
new file mode 100644
index 0000000..b68c8c3
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/ClientTest.php
@@ -0,0 +1,294 @@
+assertIsAtolable(new Client(), []);
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и приведение к json
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::jsonSerialize
+ * @covers \AtolOnline\Entities\Client::setName
+ * @covers \AtolOnline\Entities\Client::setPhone
+ * @covers \AtolOnline\Entities\Client::setEmail
+ * @covers \AtolOnline\Entities\Client::setInn
+ * @covers \AtolOnline\Entities\Client::getName
+ * @covers \AtolOnline\Entities\Client::getPhone
+ * @covers \AtolOnline\Entities\Client::getEmail
+ * @covers \AtolOnline\Entities\Client::getInn
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $this->assertIsAtolable(new Client('John Doe'), ['name' => 'John Doe']);
+ $this->assertIsAtolable(new Client(email: 'john@example.com'), ['email' => 'john@example.com']);
+ $this->assertIsAtolable(new Client(phone: '+1/22/99*73s dsdas654 5s6'), ['phone' => '+122997365456']);
+ $this->assertIsAtolable(new Client(inn: '+fasd3\qe3fs_=nac99013928czc'), ['inn' => '3399013928']);
+ $this->assertIsAtolable(
+ new Client(
+ 'John Doe',
+ 'john@example.com',
+ '+1/22/99*73s dsdas654 5s6', // +122997365456
+ '+fasd3\qe3fs_=nac99013928czc' // 3399013928
+ ), [
+ 'name' => 'John Doe',
+ 'email' => 'john@example.com',
+ 'phone' => '+122997365456',
+ 'inn' => '3399013928',
+ ]
+ );
+ }
+
+ /**
+ * Тестирует установку имён, которые приводятся к null
+ *
+ * @param mixed $name
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setName
+ * @covers \AtolOnline\Entities\Client::getName
+ * @throws TooLongClientNameException
+ */
+ public function testNullableNames(mixed $name): void
+ {
+ $this->assertNull((new Client())->setName($name)->getName());
+ }
+
+ /**
+ * Тестирует установку валидного имени
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setName
+ * @covers \AtolOnline\Entities\Client::getName
+ * @throws TooLongClientNameException
+ */
+ public function testValidName(): void
+ {
+ $name = Helpers::randomStr();
+ $this->assertEquals($name, (new Client())->setName($name)->getName());
+ }
+
+ /**
+ * Тестирует установку невалидного имени
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setName
+ * @covers \AtolOnline\Exceptions\TooLongClientNameException
+ */
+ public function testInvalidName(): void
+ {
+ $this->expectException(TooLongClientNameException::class);
+ (new Client())->setName(Helpers::randomStr(400));
+ }
+
+ /**
+ * Тестирует установку телефонов, которые приводятся к null
+ *
+ * @param mixed $phone
+ * @dataProvider providerNullablePhones
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setPhone
+ * @covers \AtolOnline\Entities\Client::getPhone
+ * @throws InvalidPhoneException
+ */
+ public function testNullablePhones(mixed $phone): void
+ {
+ $this->assertNull((new Client())->setPhone($phone)->getPhone());
+ }
+
+ /**
+ * Тестирует установку валидного телефона
+ *
+ * @throws InvalidPhoneException
+ * @todo актуализировать при доработатанной валидации
+ * @dataProvider providerValidPhones
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setPhone
+ * @covers \AtolOnline\Entities\Client::getPhone
+ */
+ public function testValidPhone(string $input, string $output): void
+ {
+ $this->assertEquals($output, (new Client())->setPhone($input)->getPhone());
+ }
+
+ /**
+ * Тестирует установку невалидного телефона
+ *
+ * @throws InvalidPhoneException
+ * @todo актуализировать при доработатанной валидации
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setPhone
+ * @covers \AtolOnline\Exceptions\InvalidPhoneException
+ */
+ public function testInvalidPhoneException(): void
+ {
+ $this->expectException(InvalidPhoneException::class);
+ (new Client())->setPhone(Helpers::randomStr(500));
+ }
+
+ /**
+ * Тестирует установку валидных email-ов
+ *
+ * @param mixed $email
+ * @dataProvider providerValidEmails
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setEmail
+ * @covers \AtolOnline\Entities\Client::getEmail
+ * @throws TooLongEmailException
+ * @throws InvalidEmailException
+ */
+ public function testValidEmails(mixed $email): void
+ {
+ $this->assertEquals($email, (new Client())->setEmail($email)->getEmail());
+ }
+
+ /**
+ * Тестирует установку слишком длинного email
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setEmail
+ * @covers \AtolOnline\Exceptions\TooLongEmailException
+ * @throws TooLongEmailException
+ * @throws InvalidEmailException
+ */
+ public function testTooLongEmail(): void
+ {
+ $this->expectException(TooLongEmailException::class);
+ (new Client())->setEmail(Helpers::randomStr(65));
+ }
+
+ /**
+ * Тестирует установку невалидного email
+ *
+ * @param mixed $email
+ * @dataProvider providerInvalidEmails
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setEmail
+ * @covers \AtolOnline\Exceptions\InvalidEmailException
+ * @throws TooLongEmailException
+ * @throws InvalidEmailException
+ */
+ public function testInvalidEmail(mixed $email): void
+ {
+ $this->expectException(InvalidEmailException::class);
+ (new Client())->setEmail($email);
+ }
+
+ /**
+ * Тестирует исключение о корректной длине ИНН
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setInn
+ * @covers \AtolOnline\Entities\Client::getInn
+ * @throws InvalidInnLengthException
+ */
+ public function testValidInn(): void
+ {
+ $this->assertEquals('1234567890', (new Client())->setInn('1234567890')->getInn());
+ $this->assertEquals('123456789012', (new Client())->setInn('123456789012')->getInn());
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (10 цифр)
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn10(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new Client())->setInn('12345678901');
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (12 цифр)
+ *
+ * @covers \AtolOnline\Entities\Client
+ * @covers \AtolOnline\Entities\Client::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn12(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new Client())->setInn('1234567890123');
+ }
+
+ /**
+ * Тестирует обращение к атрибутам объекта как к элементам массива
+ *
+ * @covers \AtolOnline\Entities\Entity::offsetGet
+ * @covers \AtolOnline\Entities\Entity::offsetExists
+ * @return void
+ */
+ public function testOffsetGetExists(): void
+ {
+ $client = new Client('John Doe');
+ $this->assertEquals('John Doe', $client['name']);
+ $this->assertTrue(isset($client['name']));
+ $this->assertFalse(isset($client['qwerty']));
+ }
+
+ /**
+ * Тестирует выброс исключения при попытке задать значение атрибуту объекта как элементу массива
+ *
+ * @covers \AtolOnline\Entities\Entity::offsetSet
+ * @return void
+ */
+ public function testBadMethodCallExceptionBySet(): void
+ {
+ $this->expectException(BadMethodCallException::class);
+ $client = new Client('John Doe');
+ $client['name'] = 'qwerty';
+ }
+
+ /**
+ * Тестирует выброс исключения при попытке удалить значение атрибута объекта как элемент массива
+ *
+ * @covers \AtolOnline\Entities\Entity::offsetUnset
+ * @return void
+ */
+ public function testBadMethodCallExceptionByUnset(): void
+ {
+ $this->expectException(BadMethodCallException::class);
+ $client = new Client('John Doe');
+ unset($client['name']);
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/CompanyTest.php b/tests/AtolOnline/Tests/Entities/CompanyTest.php
new file mode 100644
index 0000000..862c68e
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/CompanyTest.php
@@ -0,0 +1,155 @@
+assertIsAtolable(new Company(
+ $email = 'company@example.com',
+ $sno = SnoTypes::OSN,
+ $inn = '1234567890',
+ $payment_address = 'https://example.com',
+ ), [
+ 'email' => $email,
+ 'sno' => $sno,
+ 'inn' => $inn,
+ 'payment_address' => $payment_address,
+ ]);
+ }
+
+ /**
+ * Тестирует исключение о слишком длинном email
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setEmail
+ * @covers \AtolOnline\Exceptions\TooLongEmailException
+ * @throws InvalidEmailException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongEmailException
+ * @throws TooLongPaymentAddressException
+ */
+ public function testEmailTooLongException()
+ {
+ $this->expectException(TooLongEmailException::class);
+ new Company(Helpers::randomStr(65), SnoTypes::OSN, '1234567890', 'https://example.com');
+ }
+
+ /**
+ * Тестирует исключение о невалидном email
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setEmail
+ * @covers \AtolOnline\Exceptions\InvalidEmailException
+ */
+ public function testInvalidEmailException()
+ {
+ $this->expectException(InvalidEmailException::class);
+ new Company('company@examas%^*.com', SnoTypes::OSN, '1234567890', 'https://example.com');
+ }
+
+ /**
+ * Тестирует исключение о слишком длинном платёжном адресе
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setSno
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ */
+ public function testInvalidSnoException()
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ new Company('company@example.com', 'test', '1234567890', 'https://example.com');
+ }
+
+ /**
+ * Тестирует исключение о слишком длинном платёжном адресе
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidEmailException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongEmailException
+ * @throws TooLongPaymentAddressException
+ */
+ public function testInvalidInnLengthException()
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ new Company('company@example.com', SnoTypes::OSN, Helpers::randomStr(13), 'https://example.com');
+ }
+
+ /**
+ * Тестирует исключение о слишком длинном платёжном адресе
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setPaymentAddress
+ * @covers \AtolOnline\Exceptions\TooLongPaymentAddressException
+ * @throws InvalidEmailException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPaymentAddressException
+ * @throws TooLongEmailException
+ * @throws TooLongPaymentAddressException
+ */
+ public function testTooLongPaymentAddressException()
+ {
+ $this->expectException(TooLongPaymentAddressException::class);
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', Helpers::randomStr(257));
+ }
+
+ /**
+ * Тестирует исключение о невалидном платёжном адресе
+ *
+ * @covers \AtolOnline\Entities\Company
+ * @covers \AtolOnline\Entities\Company::setPaymentAddress
+ * @covers \AtolOnline\Exceptions\InvalidPaymentAddressException
+ */
+ public function testInvalidPaymentAddressException()
+ {
+ $this->expectException(InvalidPaymentAddressException::class);
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', '');
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/CorrectionInfoTest.php b/tests/AtolOnline/Tests/Entities/CorrectionInfoTest.php
new file mode 100644
index 0000000..3cf7e8d
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/CorrectionInfoTest.php
@@ -0,0 +1,107 @@
+assertIsAtolable(
+ new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', $number = Helpers::randomStr()),
+ [
+ 'type' => CorrectionTypes::SELF,
+ 'base_date' => '01.01.2021',
+ 'base_number' => $number,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует исключение при некорректном типе
+ *
+ * @covers \AtolOnline\Entities\CorrectionInfo
+ * @covers \AtolOnline\Entities\CorrectionInfo::setType
+ * @covers \AtolOnline\Enums\CorrectionTypes::isValid
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ * @return void
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ * @throws InvalidEnumValueException
+ */
+ public function testInvalidEnumValueException(): void
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ $this->expectExceptionMessage('Некорректное значение AtolOnline\Enums\CorrectionTypes::wrong_value');
+ new CorrectionInfo('wrong_value', '01.01.2021', Helpers::randomStr());
+ }
+
+ /**
+ * Тестирует исключение при некорректной дате
+ *
+ * @covers \AtolOnline\Entities\CorrectionInfo
+ * @covers \AtolOnline\Entities\CorrectionInfo::setDate
+ * @covers \AtolOnline\Enums\CorrectionTypes::isValid
+ * @covers \AtolOnline\Exceptions\InvalidCorrectionDateException
+ * @return void
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ * @throws InvalidEnumValueException
+ */
+ public function testInvalidCorrectionDateException(): void
+ {
+ $this->expectException(InvalidCorrectionDateException::class);
+ new CorrectionInfo(CorrectionTypes::SELF, Helpers::randomStr(), Helpers::randomStr());
+ }
+
+ /**
+ * Тестирует исключение при некорректной дате
+ *
+ * @covers \AtolOnline\Entities\CorrectionInfo
+ * @covers \AtolOnline\Entities\CorrectionInfo::setNumber
+ * @covers \AtolOnline\Enums\CorrectionTypes::isValid
+ * @covers \AtolOnline\Exceptions\EmptyCorrectionNumberException
+ * @return void
+ */
+ public function testEmptyCorrectionNumberException(): void
+ {
+ $this->expectException(EmptyCorrectionNumberException::class);
+ new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', "\n\r\t\0");
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/CorrectionTest.php b/tests/AtolOnline/Tests/Entities/CorrectionTest.php
new file mode 100644
index 0000000..7cef2b2
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/CorrectionTest.php
@@ -0,0 +1,117 @@
+newCorrection();
+ $this->assertIsAtolable($correction);
+ }
+
+ /**
+ * Тестирует установку валидного кассира
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Correction::setCashier
+ * @covers \AtolOnline\Entities\Correction::getCashier
+ * @covers \AtolOnline\Entities\Correction::jsonSerialize
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongCashierException
+ */
+ public function testCashier(): void
+ {
+ $correction = $this->newCorrection()->setCashier(Helpers::randomStr());
+ $this->assertArrayHasKey('cashier', $correction->jsonSerialize());
+ $this->assertEquals($correction->getCashier(), $correction->jsonSerialize()['cashier']);
+ }
+
+ /**
+ * Тестирует обнуление кассира
+ *
+ * @param mixed $param
+ * @return void
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Correction::setCashier
+ * @covers \AtolOnline\Entities\Correction::getCashier
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongCashierException
+ */
+ public function testNullableCashier(mixed $param): void
+ {
+ $this->assertNull($this->newCorrection()->setCashier($param)->getCashier());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинного кассира (лол)
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Correction::setCashier
+ * @covers \AtolOnline\Exceptions\TooLongCashierException
+ * @throws EmptyCorrectionNumberException
+ * @throws InvalidCorrectionDateException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongCashierException
+ */
+ public function testTooLongCashierException(): void
+ {
+ $this->expectException(TooLongCashierException::class);
+ $this->newCorrection()->setCashier(Helpers::randomStr(Constraints::MAX_LENGTH_CASHIER_NAME + 1));
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/ItemTest.php b/tests/AtolOnline/Tests/Entities/ItemTest.php
new file mode 100644
index 0000000..3be1b50
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/ItemTest.php
@@ -0,0 +1,802 @@
+assertIsAtolable(
+ new Item('test item', 2, 3),
+ [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинного имени предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setName
+ * @covers \AtolOnline\Exceptions\TooLongItemNameException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testTooLongItemNameException(): void
+ {
+ $this->expectException(TooLongItemNameException::class);
+ new Item(Helpers::randomStr(Constraints::MAX_LENGTH_ITEM_NAME + 1), 2, 3);
+ }
+
+ /**
+ * Тестирует выброс исключения при установке пустого имени предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setName
+ * @covers \AtolOnline\Exceptions\EmptyItemNameException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testEmptyItemNameException(): void
+ {
+ $this->expectException(EmptyItemNameException::class);
+ new Item(" \n\r\t\0 ", 2, 3);
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком высокой цены предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setPrice
+ * @covers \AtolOnline\Exceptions\TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testTooHighPriceException(): void
+ {
+ $this->expectException(TooHighItemPriceException::class);
+ new Item('test', Constraints::MAX_COUNT_ITEM_PRICE + 0.1, 3);
+ }
+
+ /**
+ * Тестирует выброс исключения при получении слишком высокой стоимости предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setPrice
+ * @covers \AtolOnline\Exceptions\TooHighItemSumException
+ * @throws TooHighItemSumException
+ */
+ public function testTooHighSumException(): void
+ {
+ $this->expectException(TooHighItemSumException::class);
+ (new Item('test', Constraints::MAX_COUNT_ITEM_PRICE, Constraints::MAX_COUNT_ITEM_QUANTITY))->getSum();
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком высокой цены предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setPrice
+ * @covers \AtolOnline\Exceptions\NegativeItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testNegativeItemPriceException(): void
+ {
+ $this->expectException(NegativeItemPriceException::class);
+ new Item('test', -0.01, 3);
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком большого количества предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setQuantity
+ * @covers \AtolOnline\Exceptions\TooHighItemQuantityException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testTooHighItemQuantityException(): void
+ {
+ $this->expectException(TooHighItemQuantityException::class);
+ new Item('test', 2, Constraints::MAX_COUNT_ITEM_QUANTITY + 1);
+ }
+
+ /**
+ * Тестирует выброс исключения при установке отрицательного количества предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item
+ * @covers \AtolOnline\Entities\Item::setQuantity
+ * @covers \AtolOnline\Exceptions\NegativeItemQuantityException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ */
+ public function testNegativeItemQuantityException(): void
+ {
+ $this->expectException(NegativeItemQuantityException::class);
+ new Item('test', 2, -0.01);
+ }
+
+ /**
+ * Тестирует обнуление единицы измерения
+ *
+ * @param mixed $param
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Item::setMeasurementUnit
+ * @covers \AtolOnline\Entities\Item::getMeasurementUnit
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooLongMeasurementUnitException
+ * @throws TooManyException
+ * @throws NegativeItemQuantityException
+ */
+ public function testNullableMeasurementUnit(mixed $param): void
+ {
+ $this->assertNull((new Item('test item', 2, 3))->setMeasurementUnit($param)->getMeasurementUnit());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинной единицы измерения
+ *
+ * @covers \AtolOnline\Entities\Item::setMeasurementUnit
+ * @covers \AtolOnline\Exceptions\TooLongMeasurementUnitException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooLongMeasurementUnitException
+ * @throws TooManyException
+ */
+ public function testTooLongMeasurementUnitException(): void
+ {
+ $this->expectException(TooLongMeasurementUnitException::class);
+ (new Item('test item', 2, 3))
+ ->setMeasurementUnit(Helpers::randomStr(Constraints::MAX_LENGTH_MEASUREMENT_UNIT + 1));
+ }
+
+ /**
+ * Тестирует сеттеры-геттеры валидных перечислимых значений атрибутов
+ *
+ * @covers \AtolOnline\Entities\Item::setPaymentMethod
+ * @covers \AtolOnline\Entities\Item::getPaymentMethod
+ * @covers \AtolOnline\Entities\Item::setPaymentObject
+ * @covers \AtolOnline\Entities\Item::getPaymentObject
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooLongItemNameException
+ * @throws Exception
+ */
+ public function testValidEnums(): void
+ {
+ $item = new Item('test item', 2, 3);
+ $this->assertEquals(
+ PaymentMethods::ADVANCE,
+ $item->setPaymentMethod(PaymentMethods::ADVANCE)->getPaymentMethod()
+ );
+ $this->assertEquals(
+ PaymentObjects::COMMODITY,
+ $item->setPaymentObject(PaymentObjects::COMMODITY)->getPaymentObject()
+ );
+ $this->assertIsAtolable($item, [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'payment_method' => 'advance',
+ 'payment_object' => 'commodity',
+ ]);
+ }
+
+ /**
+ * Тестирует установку невалидного способа оплаты
+ *
+ * @covers \AtolOnline\Entities\Item::setPaymentMethod
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooLongItemNameException
+ */
+ public function testInvalidPaymentMethod(): void
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ $this->expectExceptionMessage('Некорректное значение AtolOnline\Enums\PaymentMethods::wrong_value');
+ (new Item('test item', 2, 3))->setPaymentMethod('wrong_value');
+ }
+
+ /**
+ * Тестирует установку невалидного предмета расчёта
+ *
+ * @covers \AtolOnline\Entities\Item::setPaymentObject
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooLongItemNameException
+ */
+ public function testInvalidPaymentObject(): void
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ $this->expectExceptionMessage('Некорректное значение AtolOnline\Enums\PaymentObjects::wrong_value');
+ (new Item('test item', 2, 3))->setPaymentObject('wrong_value');
+ }
+
+ /**
+ * Тестирует установку ставки НДС по строковой константе типа ставки
+ *
+ * @covers \AtolOnline\Entities\Item::setVat
+ * @covers \AtolOnline\Entities\Item::getVat
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooHighItemSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testValidVatByString(): void
+ {
+ $item = (new Item('test item', 2, 3))->setVat(VatTypes::VAT20);
+ $this->assertIsSameClass(Vat::class, $item->getVat());
+ $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
+ $this->assertEquals($item->getSum(), $item->getVat()->getSum());
+ $this->assertIsAtolable($item, [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'vat' => [
+ 'type' => 'vat20',
+ 'sum' => 1.2,
+ ],
+ ]);
+ }
+
+ /**
+ * Тестирует установку ставки НДС объектом
+ *
+ * @covers \AtolOnline\Entities\Item::setVat
+ * @covers \AtolOnline\Entities\Item::getVat
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooHighItemSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testValidVatByObject(): void
+ {
+ $vat = new Vat(VatTypes::VAT20, 4000);
+ $item = (new Item('test item', 2, 3))->setVat($vat);
+ $this->assertIsSameClass(Vat::class, $item->getVat());
+ $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
+ $this->assertEquals($item->getSum(), $item->getVat()->getSum());
+ $this->assertIsAtolable($item, [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'vat' => [
+ 'type' => 'vat20',
+ 'sum' => 1.2,
+ ],
+ ]);
+ }
+
+ /**
+ * Тестирует обнуление ставки НДС
+ *
+ * @param mixed $vat
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Item::setVat
+ * @covers \AtolOnline\Entities\Item::getVat
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws InvalidEnumValueException
+ */
+ public function testNullableVatByString(mixed $vat): void
+ {
+ $item = (new Item('test item', 2, 3))->setVat($vat);
+ $this->assertNull($item->getVat());
+ }
+
+ /**
+ * Тестирует установку атрибутов агента
+ *
+ * @covers \AtolOnline\Entities\Item::setAgentInfo
+ * @covers \AtolOnline\Entities\Item::getAgentInfo
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPhoneException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooLongPayingAgentOperationException
+ * @throws TooManyException
+ */
+ public function testAgentInfo(): void
+ {
+ $agent_info = new AgentInfo(
+ AgentTypes::ANOTHER,
+ new PayingAgent('test', ['+79518888888']),
+ new ReceivePaymentsOperator(['+79519999999']),
+ new MoneyTransferOperator('MTO Name', '9876543210', 'London', ['+79517777777']),
+ );
+ $item = (new Item('test item', 2, 3))->setAgentInfo($agent_info);
+ $this->assertEquals($agent_info, $item->getAgentInfo());
+ }
+
+ /**
+ * Тестирует установку поставщика
+ *
+ * @covers \AtolOnline\Entities\Item::setSupplier
+ * @covers \AtolOnline\Entities\Item::getSupplier
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPhoneException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testSupplier(): void
+ {
+ $supplier = new Supplier(
+ 'some name',
+ '+fasd3\qe3fs_=nac99013928czc',
+ ['+122997365456'],
+ );
+ $item = (new Item('test item', 2, 3))->setSupplier($supplier);
+ $this->assertEquals($supplier, $item->getSupplier());
+ $this->assertIsAtolable($item, [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'supplier_info' => [
+ 'name' => 'some name',
+ 'inn' => '3399013928',
+ 'phones' => ['+122997365456'],
+ ],
+ ]);
+ }
+
+ /**
+ * Тестирует установку валидных пользовательских данных
+ *
+ * @covers \AtolOnline\Entities\Item::setUserData
+ * @covers \AtolOnline\Entities\Item::getUserData
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooLongUserdataException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testValidUserdata(): void
+ {
+ $this->assertIsAtolable(
+ (new Item('test item', 2, 3))
+ ->setUserData($user_data = Helpers::randomStr(Constraints::MAX_LENGTH_USER_DATA)),
+ [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'user_data' => $user_data,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует обнуление пользовательских данных
+ *
+ * @param mixed $param
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Item::setUserData
+ * @covers \AtolOnline\Entities\Item::getUserData
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws NegativeItemQuantityException
+ * @throws TooLongUserdataException
+ */
+ public function testNullableUserData(mixed $param): void
+ {
+ $item = new Item('test item', 2, 3);
+ $this->assertNull($item->setUserData($param)->getUserData());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинных польз. данных
+ *
+ * @covers \AtolOnline\Entities\Item::setUserData
+ * @covers \AtolOnline\Exceptions\TooLongUserdataException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooLongUserdataException
+ * @throws TooManyException
+ */
+ public function testTooLongUserdataException(): void
+ {
+ $this->expectException(TooLongUserdataException::class);
+ (new Item('test item', 2, 3))->setUserData(Helpers::randomStr(Constraints::MAX_LENGTH_USER_DATA + 1));
+ }
+
+ /**
+ * Тестирует установку кода страны происхождения товара
+ *
+ * @covers \AtolOnline\Entities\Item::setCountryCode
+ * @covers \AtolOnline\Entities\Item::getCountryCode
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws InvalidOKSMCodeException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testCountryCode(): void
+ {
+ $this->assertIsAtolable(
+ (new Item('test item', 2, 3))->setCountryCode('800'),
+ [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'country_code' => '800',
+ ]
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при установке невалидного кода страны происхождения товара
+ *
+ * @covers \AtolOnline\Entities\Item::setCountryCode
+ * @covers \AtolOnline\Exceptions\InvalidOKSMCodeException
+ * @throws EmptyItemNameException
+ * @throws InvalidOKSMCodeException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testInvalidOKSMCodeException(): void
+ {
+ $this->expectException(InvalidOKSMCodeException::class);
+ (new Item('test item', 2, 3))->setCountryCode(Helpers::randomStr());
+ }
+
+ /**
+ * Тестирует установку валидного кода таможенной декларации
+ *
+ * @covers \AtolOnline\Entities\Item::getDeclarationNumber
+ * @covers \AtolOnline\Entities\Item::setDeclarationNumber
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws InvalidDeclarationNumberException
+ * @throws Exception
+ */
+ public function testValidDeclarationNumber(): void
+ {
+ $this->assertIsAtolable(
+ (new Item('test item', 2, 3))
+ ->setDeclarationNumber($code = Helpers::randomStr()),
+ [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'declaration_number' => $code,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком короткого кода таможенной декларации
+ *
+ * @covers \AtolOnline\Entities\Item::setDeclarationNumber
+ * @covers \AtolOnline\Exceptions\InvalidDeclarationNumberException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws InvalidDeclarationNumberException
+ * @throws TooManyException
+ */
+ public function testInvalidDeclarationNumberExceptionMin(): void
+ {
+ $this->expectException(InvalidDeclarationNumberException::class);
+ (new Item('test item', 2, 3))
+ ->setDeclarationNumber(Helpers::randomStr(Constraints::MIN_LENGTH_DECLARATION_NUMBER - 1));
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинного кода таможенной декларации
+ *
+ * @covers \AtolOnline\Entities\Item::setDeclarationNumber
+ * @covers \AtolOnline\Exceptions\InvalidDeclarationNumberException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws InvalidDeclarationNumberException
+ * @throws TooManyException
+ */
+ public function testInvalidDeclarationNumberExceptionMax(): void
+ {
+ $this->expectException(InvalidDeclarationNumberException::class);
+ (new Item('test item', 2, 3))
+ ->setDeclarationNumber(Helpers::randomStr(Constraints::MAX_LENGTH_DECLARATION_NUMBER + 1));
+ }
+
+ /**
+ * Тестирует установку акциза и расчёт суммы с его учётом
+ *
+ * @covers \AtolOnline\Entities\Item::setExcise
+ * @covers \AtolOnline\Entities\Item::getExcise
+ * @covers \AtolOnline\Entities\Item::getSum
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ * @throws NegativeItemExciseException
+ * @throws Exception
+ */
+ public function testExcise(): void
+ {
+ $this->assertIsAtolable(
+ (new Item('test item', 2, 3))->setExcise(1),
+ [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 7,
+ 'excise' => 1,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком отрицательного акциза
+ *
+ * @covers \AtolOnline\Entities\Item::setExcise
+ * @covers \AtolOnline\Exceptions\NegativeItemExciseException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ * @throws NegativeItemExciseException
+ */
+ public function testNegativeItemExciseException(): void
+ {
+ $this->expectException(NegativeItemExciseException::class);
+ (new Item('test item', 2, 3))->setExcise(-1);
+ }
+
+ /**
+ * Тестирует установку валидного кода товара
+ *
+ * @covers \AtolOnline\Entities\Item::setCode
+ * @covers \AtolOnline\Entities\Item::getCode
+ * @covers \AtolOnline\Entities\Item::getCodeHex
+ * @covers \AtolOnline\Entities\Item::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws TooLongItemCodeException
+ * @throws Exception
+ */
+ public function testValidNomenclatureCode(): void
+ {
+ $code = Helpers::randomStr(Constraints::MAX_LENGTH_ITEM_CODE);
+ $encoded = trim(preg_replace('/([\dA-Fa-f]{2})/', '$1 ', bin2hex($code)));
+
+ $item = (new Item('test item', 2, 3))->setCode($code);
+ $this->assertEquals($code, $item->getCode());
+ $this->assertEquals($encoded, $item->getCodeHex());
+
+ $decoded = hex2bin(str_replace(' ', '', $item->getCodeHex()));
+ $this->assertEquals($decoded, $item->getCode());
+
+ $this->assertIsAtolable($item, [
+ 'name' => 'test item',
+ 'price' => 2,
+ 'quantity' => 3,
+ 'sum' => 6,
+ 'nomenclature_code' => $item->getCodeHex(),
+ ]);
+ }
+
+ /**
+ * Тестирует обнуление кода товара
+ *
+ * @param mixed $param
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Item::setCode
+ * @covers \AtolOnline\Entities\Item::getCode
+ * @covers \AtolOnline\Entities\Item::getCodeHex
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemCodeException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testNullableCode(mixed $param): void
+ {
+ $item = (new Item('test item', 2, 3))->setCode($param);
+ $this->assertNull($item->getCode());
+ $this->assertNull($item->getCodeHex());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком отрицательного акциза
+ *
+ * @covers \AtolOnline\Entities\Item::setCode
+ * @covers \AtolOnline\Exceptions\TooLongItemCodeException
+ * @throws TooLongItemNameException
+ * @throws TooHighItemPriceException
+ * @throws TooManyException
+ * @throws NegativeItemPriceException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemQuantityException
+ */
+ public function testTooLongItemCodeException(): void
+ {
+ $this->expectException(TooLongItemCodeException::class);
+ (new Item('test item', 2, 3))->setCode(Helpers::randomStr(Constraints::MAX_LENGTH_ITEM_CODE + 1));
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/KktEntityTest.php b/tests/AtolOnline/Tests/Entities/KktEntityTest.php
new file mode 100644
index 0000000..f27758e
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/KktEntityTest.php
@@ -0,0 +1,137 @@
+ '00107703864827',
+ 'registrationNumber' => '0000000003027865',
+ 'deviceNumber' => 'KKT024219',
+ 'fiscalizationDate' => '2019-07-22T14:03:00+00:00',
+ 'fiscalStorageExpiration' => '2020-11-02T21:00:00+00:00',
+ 'signedDocuments' => 213350,
+ 'fiscalStoragePercentageUse' => 85.34,
+ 'fiscalStorageINN' => '3026455760',
+ 'fiscalStorageSerialNumber' => '9999078902004339',
+ 'fiscalStoragePaymentAddress' => 'test.qa.ru',
+ 'groupCode' => 'test-qa-ru_14605',
+ 'timestamp' => '2019-12-05T10:45:30+00:00',
+ 'isShiftOpened' => true,
+ 'shiftNumber' => 126,
+ 'shiftReceipt' => 2278,
+ //'unsentDocs' => 123,
+ 'firstUnsetDocTimestamp' => 'there must be timestamp, but we want to get exception here to get string',
+ 'networkErrorCode' => 2,
+ ];
+
+ /**
+ * Тестирует создание объекта ККТ с валидными данными
+ *
+ * @covers \AtolOnline\Entities\Kkt::__construct
+ * @covers \AtolOnline\Entities\Kkt::__get
+ * @covers \AtolOnline\Entities\Kkt::jsonSerialize
+ * @covers \AtolOnline\Entities\Kkt::__toString
+ * @throws Exception
+ */
+ public function testConstructor(): void
+ {
+ $kkt = new Kkt((object)$this->sample_data);
+ $this->assertIsSameClass(Kkt::class, $kkt);
+ $this->assertIsAtolable($kkt);
+ }
+
+ /**
+ * Тестирует исключение при попытке создать объект ККТ без данных от монитора
+ *
+ * @covers \AtolOnline\Entities\Kkt::__construct
+ * @covers \AtolOnline\Exceptions\EmptyMonitorDataException
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ */
+ public function testEmptyMonitorDataException(): void
+ {
+ $this->expectException(EmptyMonitorDataException::class);
+ new Kkt((object)[]);
+ }
+
+ /**
+ * Тестирует исключение при попытке создать объект ККТ без данных от монитора
+ *
+ * @covers \AtolOnline\Entities\Kkt::__construct
+ * @covers \AtolOnline\Exceptions\NotEnoughMonitorDataException
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ */
+ public function testNotEnoughMonitorDataException(): void
+ {
+ $this->expectException(NotEnoughMonitorDataException::class);
+ new Kkt((object)[
+ 'fiscalizationDate' => '2021-11-20T10:21:00+00:00',
+ ]);
+ }
+
+ /**
+ * Тестирует получение атрибутов через магический геттер
+ *
+ * @covers \AtolOnline\Entities\Kkt::__get
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ */
+ public function testMagicGetter(): void
+ {
+ $kkt = new Kkt((object)$this->sample_data);
+
+ // string
+ $this->assertNotNull($kkt->serialNumber);
+ $this->assertIsString($kkt->serialNumber);
+ $this->assertEquals($this->sample_data['serialNumber'], $kkt->serialNumber);
+
+ // int
+ $this->assertNotNull($kkt->signedDocuments);
+ $this->assertIsInt($kkt->signedDocuments);
+
+ // float
+ $this->assertNotNull($kkt->signedDocuments);
+ $this->assertIsFloat($kkt->fiscalStoragePercentageUse);
+
+ // null
+ $this->assertNull($kkt->unsentDocs);
+
+ // DateTime
+ $this->assertNotNull($kkt->fiscalizationDate);
+ $this->assertIsSameClass(DateTime::class, $kkt->fiscalizationDate);
+ }
+
+ /**
+ * Тестирует исключение при попытке получить некорректный DateTime через магический геттер
+ *
+ * @covers \AtolOnline\Entities\Kkt::__get
+ * @throws EmptyMonitorDataException
+ * @throws NotEnoughMonitorDataException
+ */
+ public function testDateTimeException(): void
+ {
+ $this->expectException(Exception::class);
+ (new Kkt((object)$this->sample_data))->firstUnsetDocTimestamp;
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/MoneyTransferOperatorTest.php b/tests/AtolOnline/Tests/Entities/MoneyTransferOperatorTest.php
new file mode 100644
index 0000000..f077466
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/MoneyTransferOperatorTest.php
@@ -0,0 +1,179 @@
+assertEquals('[]', (string)(new MoneyTransferOperator()));
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и корректное приведение к json
+ *
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::jsonSerialize
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setName
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getName
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setPhones
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getPhones
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setInn
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getInn
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setAddress
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getAddress
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $this->assertIsAtolable(new MoneyTransferOperator('some name'), ['name' => 'some name']);
+ $this->assertIsAtolable(new MoneyTransferOperator(inn: '+fasd3\qe3fs_=nac99013928czc'), ['inn' => '3399013928']);
+ $this->assertIsAtolable(new MoneyTransferOperator(address: 'London'), ['address' => 'London']);
+ $this->assertIsAtolable(new MoneyTransferOperator(phones: ['+122997365456']), ['phones' => ['+122997365456']]);
+ $this->assertIsAtolable(new MoneyTransferOperator(
+ 'some name',
+ '+fasd3\qe3fs_=nac99013928czc',
+ 'London',
+ ['+122997365456'],
+ ), [
+ 'name' => 'some name',
+ 'inn' => '3399013928',
+ 'address' => 'London',
+ 'phones' => ['+122997365456'],
+ ]);
+ }
+
+ /**
+ * Тестирует установку имён, которые приводятся к null
+ *
+ * @param mixed $name
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setName
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getName
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testNullableOperations(mixed $name): void
+ {
+ $this->assertNull((new MoneyTransferOperator($name))->getName());
+ }
+
+ /**
+ * Провайдер массивов телефонов, которые приводятся к null
+ *
+ * @return array
+ */
+ public function providerNullablePhonesArrays(): array
+ {
+ return [
+ [[]],
+ [null],
+ [collect()],
+ ];
+ }
+
+ /**
+ * Тестирует установку пустых телефонов
+ *
+ * @dataProvider providerNullablePhonesArrays
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setPhones
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getPhones
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testNullablePhones(mixed $phones): void
+ {
+ $agent = new MoneyTransferOperator(phones: $phones);
+ $this->assertIsCollection($agent->getPhones());
+ $this->assertTrue($agent->getPhones()->isEmpty());
+ }
+
+ /**
+ * Тестирует установку невалидных телефонов
+ *
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setPhones
+ * @covers \AtolOnline\Exceptions\InvalidPhoneException
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidPhoneException(): void
+ {
+ $this->expectException(InvalidPhoneException::class);
+ (new MoneyTransferOperator(phones: [
+ '12345678901234567', // good
+ '+123456789012345678', // good
+ '12345678901234567890', // bad
+ '+12345678901234567890', // bad
+ ]));
+ }
+
+ /**
+ * Тестирует исключение о корректной длине ИНН
+ *
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setInn
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::getInn
+ * @throws InvalidInnLengthException
+ */
+ public function testValidInn(): void
+ {
+ $this->assertEquals('1234567890', (new MoneyTransferOperator())->setInn('1234567890')->getInn());
+ $this->assertEquals('123456789012', (new MoneyTransferOperator())->setInn('123456789012')->getInn());
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (10 цифр)
+ *
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn10(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new MoneyTransferOperator())->setInn('12345678901');
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (12 цифр)
+ *
+ * @covers \AtolOnline\Entities\MoneyTransferOperator
+ * @covers \AtolOnline\Entities\MoneyTransferOperator::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn12(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new MoneyTransferOperator())->setInn('1234567890123');
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/PayingAgentTest.php b/tests/AtolOnline/Tests/Entities/PayingAgentTest.php
new file mode 100644
index 0000000..62ed0a4
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/PayingAgentTest.php
@@ -0,0 +1,147 @@
+assertEquals('[]', (string)(new PayingAgent()));
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и корректное приведение к json
+ *
+ * @covers \AtolOnline\Entities\PayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::jsonSerialize
+ * @covers \AtolOnline\Entities\PayingAgent::setOperation
+ * @covers \AtolOnline\Entities\PayingAgent::setPhones
+ * @covers \AtolOnline\Entities\PayingAgent::getOperation
+ * @covers \AtolOnline\Entities\PayingAgent::getPhones
+ * @throws InvalidPhoneException
+ * @throws TooLongPayingAgentOperationException
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $operation = Helpers::randomStr();
+ $this->assertIsAtolable(new PayingAgent(
+ $operation,
+ ['+122997365456'],
+ ), [
+ 'operation' => $operation,
+ 'phones' => ['+122997365456'],
+ ]);
+ $this->assertIsAtolable(
+ new PayingAgent($operation),
+ ['operation' => $operation]
+ );
+ $this->assertIsAtolable(
+ new PayingAgent(phones: ['+122997365456']),
+ ['phones' => ['+122997365456']]
+ );
+ }
+
+ /**
+ * Тестирует установку операций, которые приводятся к null
+ *
+ * @param mixed $operation
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\PayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::setOperation
+ * @covers \AtolOnline\Entities\PayingAgent::getOperation
+ * @throws TooLongPayingAgentOperationException
+ * @throws InvalidPhoneException
+ */
+ public function testNullableOperations(mixed $operation): void
+ {
+ $this->assertNull((new PayingAgent($operation))->getOperation());
+ }
+
+ /**
+ * Тестирует установку невалидной операции
+ *
+ * @covers \AtolOnline\Entities\PayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::setOperation
+ * @covers \AtolOnline\Exceptions\TooLongPayingAgentOperationException
+ */
+ public function testTooLongPayingAgentOperationException(): void
+ {
+ $this->expectException(TooLongPayingAgentOperationException::class);
+ (new PayingAgent())->setOperation(Helpers::randomStr(25));
+ }
+
+ /**
+ * Провайдер массивов телефонов, которые приводятся к null
+ *
+ * @return array
+ */
+ public function providerNullablePhonesArrays(): array
+ {
+ return [
+ [[]],
+ [null],
+ [collect()],
+ ];
+ }
+
+ /**
+ * Тестирует установку пустых телефонов
+ *
+ * @dataProvider providerNullablePhonesArrays
+ * @covers \AtolOnline\Entities\PayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::setPhones
+ * @covers \AtolOnline\Entities\PayingAgent::getPhones
+ * @throws InvalidPhoneException
+ * @throws TooLongPayingAgentOperationException
+ */
+ public function testNullablePhones(mixed $phones): void
+ {
+ $agent = new PayingAgent(phones: $phones);
+ $this->assertIsCollection($agent->getPhones());
+ $this->assertTrue($agent->getPhones()->isEmpty());
+ }
+
+ /**
+ * Тестирует установку невалидных телефонов
+ *
+ * @covers \AtolOnline\Entities\PayingAgent
+ * @covers \AtolOnline\Entities\PayingAgent::setPhones
+ * @covers \AtolOnline\Exceptions\InvalidPhoneException
+ * @throws InvalidPhoneException
+ */
+ public function testInvalidPhoneException(): void
+ {
+ $this->expectException(InvalidPhoneException::class);
+ (new PayingAgent())->setPhones([
+ '12345678901234567', // good
+ '+123456789012345678', // good
+ '12345678901234567890', // bad
+ '+12345678901234567890', // bad
+ ]);
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/PaymentTest.php b/tests/AtolOnline/Tests/Entities/PaymentTest.php
new file mode 100644
index 0000000..da58d5a
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/PaymentTest.php
@@ -0,0 +1,105 @@
+assertIsAtolable(
+ new Payment(PaymentTypes::ELECTRON, 123.456789),
+ [
+ 'type' => PaymentTypes::ELECTRON,
+ 'sum' => 123.46,
+ ]
+ );
+ }
+
+ /**
+ * Тестирует исключение при некорректном типе
+ *
+ * @covers \AtolOnline\Entities\Payment
+ * @covers \AtolOnline\Entities\Payment::setType
+ * @covers \AtolOnline\Enums\PaymentTypes::isValid
+ * @covers \AtolOnline\Exceptions\InvalidEnumValueException
+ * @return void
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ */
+ public function testInvalidEnumValueException(): void
+ {
+ $this->expectException(InvalidEnumValueException::class);
+ $this->expectExceptionMessage('Некорректное значение AtolOnline\Enums\PaymentTypes::123');
+ new Payment(123, 123.456789);
+ }
+
+ /**
+ * Тестирует исключение при слишком большой сумме
+ *
+ * @covers \AtolOnline\Entities\Payment
+ * @covers \AtolOnline\Entities\Payment::setSum
+ * @covers \AtolOnline\Exceptions\TooHighPaymentSumException
+ * @return void
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ */
+ public function testTooHighPaymentSumException(): void
+ {
+ $this->expectException(TooHighPaymentSumException::class);
+ new Payment(PaymentTypes::ELECTRON, Constraints::MAX_COUNT_PAYMENT_SUM + 1);
+ }
+
+ /**
+ * Тестирует исключение при отрицательной сумме
+ *
+ * @covers \AtolOnline\Entities\Payment
+ * @covers \AtolOnline\Entities\Payment::setSum
+ * @covers \AtolOnline\Exceptions\NegativePaymentSumException
+ * @return void
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ */
+ public function testNegativePaymentSumException(): void
+ {
+ $this->expectException(NegativePaymentSumException::class);
+ new Payment(PaymentTypes::ELECTRON, -1);
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/ReceiptTest.php b/tests/AtolOnline/Tests/Entities/ReceiptTest.php
new file mode 100644
index 0000000..ce3151b
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/ReceiptTest.php
@@ -0,0 +1,571 @@
+newReceipt();
+ $this->assertIsAtolable($receipt);
+ }
+
+ /**
+ * Тестирует установку данных агента
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setAgentInfo
+ * @covers \AtolOnline\Entities\Receipt::getAgentInfo
+ * @covers \AtolOnline\Entities\Receipt::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPhoneException
+ * @throws TooLongPayingAgentOperationException
+ * @throws Exception
+ */
+ public function testAgentInfo(): void
+ {
+ $agent_info = new AgentInfo(
+ AgentTypes::ANOTHER,
+ new PayingAgent('test', ['+79518888888']),
+ new ReceivePaymentsOperator(['+79519999999']),
+ new MoneyTransferOperator('MTO Name', '9876543210', 'London', ['+79517777777']),
+ );
+ $receipt = $this->newReceipt()->setAgentInfo($agent_info);
+ $this->assertArrayHasKey('agent_info', $receipt->jsonSerialize());
+ $this->assertEquals($receipt->getAgentInfo()->jsonSerialize(), $receipt->jsonSerialize()['agent_info']);
+ $this->assertArrayNotHasKey('agent_info', $receipt->setAgentInfo(null)->jsonSerialize());
+ }
+
+ /**
+ * Тестирует установку данных поставщика
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setSupplier
+ * @covers \AtolOnline\Entities\Receipt::getSupplier
+ * @covers \AtolOnline\Entities\Receipt::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws InvalidInnLengthException
+ * @throws InvalidPhoneException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testSupplier(): void
+ {
+ $supplier = new Supplier('some name', '+fasd3\qe3fs_=nac99013928czc', ['+122997365456']);
+ $receipt = $this->newReceipt()->setSupplier($supplier);
+ $this->assertArrayHasKey('supplier_info', $receipt->jsonSerialize());
+ $this->assertEquals($receipt->getSupplier()->jsonSerialize(), $receipt->jsonSerialize()['supplier_info']);
+ $this->assertArrayNotHasKey('supplier_info', $receipt->setSupplier(null)->jsonSerialize());
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче пустой коллекции предметов расчёта
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setItems
+ * @covers \AtolOnline\Collections\Items::checkCount
+ * @covers \AtolOnline\Exceptions\EmptyItemsException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws InvalidEntityInCollectionException
+ * @throws EmptyPaymentsException
+ */
+ public function testEmptyItemsException(): void
+ {
+ $this->expectException(EmptyItemsException::class);
+ new Receipt(
+ new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'),
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new Items([]),
+ new Payments($this->generatePaymentObjects())
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче коллекции предметов расчёта с некорректным содержимым
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setItems
+ * @covers \AtolOnline\Collections\Items::checkItemsClasses
+ * @covers \AtolOnline\Collections\Items::checkItemClass
+ * @covers \AtolOnline\Exceptions\InvalidEntityInCollectionException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ */
+ public function testInvalidItemInCollectionException(): void
+ {
+ $this->expectException(InvalidEntityInCollectionException::class);
+ $this->expectErrorMessage(
+ 'Коллекция AtolOnline\Collections\Items должна содержать объекты AtolOnline\Entities\Item'
+ );
+ new Receipt(
+ new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'),
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new Items(['qwerty']),
+ new Payments($this->generatePaymentObjects())
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче пустой коллекции оплат
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setPayments
+ * @covers \AtolOnline\Collections\Payments::checkCount
+ * @covers \AtolOnline\Exceptions\EmptyPaymentsException
+ * @throws TooHighPaymentSumException
+ * @throws EmptyItemNameException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws InvalidEntityInCollectionException
+ * @throws EmptyItemsException
+ */
+ public function testEmptyPaymentsException(): void
+ {
+ $this->expectException(EmptyPaymentsException::class);
+ new Receipt(
+ new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'),
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new Items([new Item('test item', 2, 3)]),
+ new Payments([])
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче коллекции предметов расчёта с некорректным содержимым
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setPayments
+ * @covers \AtolOnline\Collections\Items::checkItemsClasses
+ * @covers \AtolOnline\Collections\Items::checkItemClass
+ * @covers \AtolOnline\Exceptions\InvalidEntityInCollectionException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws TooHighItemPriceException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testInvalidPaymentInCollectionException(): void
+ {
+ $this->expectException(InvalidEntityInCollectionException::class);
+ $this->expectErrorMessage(
+ 'Коллекция AtolOnline\Collections\Payments должна содержать объекты AtolOnline\Entities\Payment'
+ );
+ (string)new Receipt(
+ new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'),
+ new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'),
+ new Items([new Item('test item', 2, 3)]),
+ new Payments(['qwerty'])
+ );
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче пустой коллекции ставок НДС
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setVats
+ * @covers \AtolOnline\Collections\Vats::checkCount
+ * @covers \AtolOnline\Exceptions\EmptyVatsException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testEmptyVatsException(): void
+ {
+ $this->expectException(EmptyVatsException::class);
+ $this->newReceipt()->setVats(new Vats([]));
+ }
+
+ /**
+ * Тестирует выброс исключения при передаче коллекции ставок НДС с некорректным содержимым
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt
+ * @covers \AtolOnline\Entities\Receipt::setVats
+ * @covers \AtolOnline\Collections\Vats::checkItemsClasses
+ * @covers \AtolOnline\Collections\Vats::checkItemClass
+ * @covers \AtolOnline\Exceptions\InvalidEntityInCollectionException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testInvalidVatInCollectionException(): void
+ {
+ $this->expectException(InvalidEntityInCollectionException::class);
+ $this->expectErrorMessage(
+ 'Коллекция AtolOnline\Collections\Vats должна содержать объекты AtolOnline\Entities\Vat'
+ );
+ (string)$this->newReceipt()->setVats(new Vats(['qwerty']));
+ }
+
+ /**
+ * Тестирует просчёт общей суммы чека и ставок НДС
+ *
+ * @covers \AtolOnline\Entities\Receipt::setVats
+ * @covers \AtolOnline\Entities\Receipt::getVats
+ * @covers \AtolOnline\Entities\Receipt::getTotal
+ * @throws TooHighItemPriceException
+ * @throws NegativeItemPriceException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws TooHighItemSumException
+ * @throws NegativePaymentSumException
+ * @throws TooHighPaymentSumException
+ * @throws EmptyItemsException
+ * @throws EmptyItemNameException
+ * @throws TooManyException
+ * @throws NegativeItemQuantityException
+ * @throws TooLongItemNameException
+ * @throws Exception
+ */
+ public function testCalculations(): void
+ {
+ $receipt = $this->newReceipt();
+ $items_total = $receipt->getItems()->pluck('sum')->sum();
+ $this->assertEquals($items_total, $receipt->getTotal());
+
+ /** @var Vat $vat */
+ $receipt->setVats(new Vats($this->generateVatObjects(2)))->getVats()
+ ->each(fn($vat) => $this->assertEquals($items_total, $vat->getSum()));
+ }
+
+ /**
+ * Тестирует установку валидного кассира
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setCashier
+ * @covers \AtolOnline\Entities\Receipt::getCashier
+ * @covers \AtolOnline\Entities\Receipt::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws TooLongCashierException
+ * @throws Exception
+ */
+ public function testCashier(): void
+ {
+ $receipt = $this->newReceipt()->setCashier(Helpers::randomStr());
+ $this->assertArrayHasKey('cashier', $receipt->jsonSerialize());
+ $this->assertEquals($receipt->getCashier(), $receipt->jsonSerialize()['cashier']);
+ }
+
+ /**
+ * Тестирует обнуление кассира
+ *
+ * @param mixed $param
+ * @return void
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Receipt::setCashier
+ * @covers \AtolOnline\Entities\Receipt::getCashier
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongCashierException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testNullableCashier(mixed $param): void
+ {
+ $this->assertNull($this->newReceipt()->setCashier($param)->getCashier());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинного кассира (лол)
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setCashier
+ * @covers \AtolOnline\Exceptions\TooLongCashierException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongCashierException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testTooLongCashierException(): void
+ {
+ $this->expectException(TooLongCashierException::class);
+ $this->newReceipt()->setCashier(Helpers::randomStr(Constraints::MAX_LENGTH_CASHIER_NAME + 1));
+ }
+
+ /**
+ * Тестирует установку дополнительного реквизита чека
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setAddCheckProps
+ * @covers \AtolOnline\Entities\Receipt::getAddCheckProps
+ * @covers \AtolOnline\Entities\Receipt::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws TooLongAddCheckPropException
+ * @throws Exception
+ */
+ public function testAddCheckProps(): void
+ {
+ $receipt = $this->newReceipt()->setAddCheckProps(Helpers::randomStr());
+ $this->assertArrayHasKey('additional_check_props', $receipt->jsonSerialize());
+ $this->assertEquals($receipt->getAddCheckProps(), $receipt->jsonSerialize()['additional_check_props']);
+ }
+
+ /**
+ * Тестирует обнуление дополнительного реквизита чека
+ *
+ * @param mixed $param
+ * @return void
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Receipt::setAddCheckProps
+ * @covers \AtolOnline\Entities\Receipt::getAddCheckProps
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongAddCheckPropException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testNullableAddCheckProps(mixed $param): void
+ {
+ $this->assertNull($this->newReceipt()->setAddCheckProps($param)->getAddCheckProps());
+ }
+
+ /**
+ * Тестирует выброс исключения при установке слишком длинного дополнительного реквизита чека
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setAddCheckProps
+ * @covers \AtolOnline\Exceptions\TooLongAddCheckPropException
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongAddCheckPropException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ */
+ public function testTooLongAddCheckPropException(): void
+ {
+ $this->expectException(TooLongAddCheckPropException::class);
+ $this->newReceipt()->setAddCheckProps(Helpers::randomStr(Constraints::MAX_LENGTH_ADD_CHECK_PROP + 1));
+ }
+
+ /**
+ * Тестирует установку дополнительного реквизита пользователя
+ *
+ * @return void
+ * @covers \AtolOnline\Entities\Receipt::setAddUserProps
+ * @covers \AtolOnline\Entities\Receipt::getAddUserProps
+ * @covers \AtolOnline\Entities\Receipt::jsonSerialize
+ * @throws EmptyItemNameException
+ * @throws EmptyItemsException
+ * @throws EmptyPaymentsException
+ * @throws InvalidEntityInCollectionException
+ * @throws InvalidEnumValueException
+ * @throws NegativeItemPriceException
+ * @throws NegativeItemQuantityException
+ * @throws NegativePaymentSumException
+ * @throws TooHighItemPriceException
+ * @throws TooHighPaymentSumException
+ * @throws TooLongItemNameException
+ * @throws TooManyException
+ * @throws Exception
+ */
+ public function testAdditionalUserProps(): void
+ {
+ $aup = new AdditionalUserProps('name', 'value');
+ $receipt = $this->newReceipt()->setAddUserProps($aup);
+ $this->assertArrayHasKey('additional_user_props', $receipt->jsonSerialize());
+ $this->assertEquals(
+ $receipt->getAddUserProps()->jsonSerialize(),
+ $receipt->jsonSerialize()['additional_user_props']
+ );
+ $this->assertArrayNotHasKey('additional_user_props', $receipt->setAddUserProps(null)->jsonSerialize());
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/ReceivePaymentsOperatorTest.php b/tests/AtolOnline/Tests/Entities/ReceivePaymentsOperatorTest.php
new file mode 100644
index 0000000..8ed134d
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/ReceivePaymentsOperatorTest.php
@@ -0,0 +1,97 @@
+assertEquals('[]', (string)(new ReceivePaymentsOperator()));
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и корректное приведение к json
+ *
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::jsonSerialize
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::setPhones
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::getPhones
+ * @throws InvalidPhoneException
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $this->assertIsAtolable(new ReceivePaymentsOperator(['+122997365456']), ['phones' => ['+122997365456']]);
+ }
+
+ /**
+ * Провайдер массивов телефонов, которые приводятся к null
+ *
+ * @return array
+ */
+ public function providerNullablePhonesArrays(): array
+ {
+ return [
+ [[]],
+ [null],
+ [collect()],
+ ];
+ }
+
+ /**
+ * Тестирует установку пустых телефонов
+ *
+ * @dataProvider providerNullablePhonesArrays
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::setPhones
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::getPhones
+ * @throws InvalidPhoneException
+ */
+ public function testNullablePhones(mixed $phones): void
+ {
+ $agent = new ReceivePaymentsOperator($phones);
+ $this->assertIsCollection($agent->getPhones());
+ $this->assertTrue($agent->getPhones()->isEmpty());
+ }
+
+ /**
+ * Тестирует установку невалидных телефонов
+ *
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator
+ * @covers \AtolOnline\Entities\ReceivePaymentsOperator::setPhones
+ * @covers \AtolOnline\Exceptions\InvalidPhoneException
+ * @throws InvalidPhoneException
+ */
+ public function testInvalidPhoneException(): void
+ {
+ $this->expectException(InvalidPhoneException::class);
+ (new ReceivePaymentsOperator([
+ '12345678901234567', // good
+ '+123456789012345678', // good
+ '12345678901234567890', // bad
+ '+12345678901234567890', // bad
+ ]));
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/SupplierTest.php b/tests/AtolOnline/Tests/Entities/SupplierTest.php
new file mode 100644
index 0000000..c14720c
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/SupplierTest.php
@@ -0,0 +1,174 @@
+assertEquals('[]', (string)(new Supplier()));
+ }
+
+ /**
+ * Тестирует конструктор с передачей значений и корректное приведение к json
+ *
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::jsonSerialize
+ * @covers \AtolOnline\Entities\Supplier::setName
+ * @covers \AtolOnline\Entities\Supplier::getName
+ * @covers \AtolOnline\Entities\Supplier::setPhones
+ * @covers \AtolOnline\Entities\Supplier::getPhones
+ * @covers \AtolOnline\Entities\Supplier::setInn
+ * @covers \AtolOnline\Entities\Supplier::getInn
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ * @throws Exception
+ */
+ public function testConstructorWithArgs(): void
+ {
+ $this->assertIsAtolable(new Supplier('some name'), ['name' => 'some name']);
+ $this->assertIsAtolable(new Supplier(inn: '+fasd3\qe3fs_=nac99013928czc'), ['inn' => '3399013928']);
+ $this->assertIsAtolable(new Supplier(phones: ['+122997365456']), ['phones' => ['+122997365456']]);
+ $this->assertIsAtolable(new Supplier(
+ 'some name',
+ '+fasd3\qe3fs_=nac99013928czc',
+ ['+122997365456'],
+ ), [
+ 'name' => 'some name',
+ 'inn' => '3399013928',
+ 'phones' => ['+122997365456'],
+ ]);
+ }
+
+ /**
+ * Тестирует установку имён, которые приводятся к null
+ *
+ * @param mixed $name
+ * @dataProvider providerNullableStrings
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setName
+ * @covers \AtolOnline\Entities\Supplier::getName
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testNullableOperations(mixed $name): void
+ {
+ $this->assertNull((new Supplier($name))->getName());
+ }
+
+ /**
+ * Провайдер массивов телефонов, которые приводятся к null
+ *
+ * @return array
+ */
+ public function providerNullablePhonesArrays(): array
+ {
+ return [
+ [[]],
+ [null],
+ [collect()],
+ ];
+ }
+
+ /**
+ * Тестирует установку пустых телефонов
+ *
+ * @dataProvider providerNullablePhonesArrays
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setPhones
+ * @covers \AtolOnline\Entities\Supplier::getPhones
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testNullablePhones(mixed $phones): void
+ {
+ $agent = new Supplier(phones: $phones);
+ $this->assertIsCollection($agent->getPhones());
+ $this->assertTrue($agent->getPhones()->isEmpty());
+ }
+
+ /**
+ * Тестирует установку невалидных телефонов
+ *
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setPhones
+ * @covers \AtolOnline\Exceptions\InvalidPhoneException
+ * @throws InvalidPhoneException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidPhoneException(): void
+ {
+ $this->expectException(InvalidPhoneException::class);
+ (new Supplier(phones: [
+ '12345678901234567', // good
+ '+123456789012345678', // good
+ '12345678901234567890', // bad
+ '+12345678901234567890', // bad
+ ]));
+ }
+
+ /**
+ * Тестирует исключение о корректной длине ИНН
+ *
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setInn
+ * @covers \AtolOnline\Entities\Supplier::getInn
+ * @throws InvalidInnLengthException
+ */
+ public function testValidInn(): void
+ {
+ $this->assertEquals('1234567890', (new Supplier())->setInn('1234567890')->getInn());
+ $this->assertEquals('123456789012', (new Supplier())->setInn('123456789012')->getInn());
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (10 цифр)
+ *
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn10(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new Supplier())->setInn('12345678901');
+ }
+
+ /**
+ * Тестирует исключение о некорректной длине ИНН (12 цифр)
+ *
+ * @covers \AtolOnline\Entities\Supplier
+ * @covers \AtolOnline\Entities\Supplier::setInn
+ * @covers \AtolOnline\Exceptions\InvalidInnLengthException
+ * @throws InvalidInnLengthException
+ */
+ public function testInvalidInn12(): void
+ {
+ $this->expectException(InvalidInnLengthException::class);
+ (new Supplier())->setInn('1234567890123');
+ }
+}
diff --git a/tests/AtolOnline/Tests/Entities/VatTest.php b/tests/AtolOnline/Tests/Entities/VatTest.php
new file mode 100644
index 0000000..33d39cd
--- /dev/null
+++ b/tests/AtolOnline/Tests/Entities/VatTest.php
@@ -0,0 +1,105 @@
+assertIsAtolable($vat, [
+ 'type' => $vat->getType(),
+ 'sum' => $vat->getCalculated(),
+ ]);
+ $this->assertEquals($type, $vat->getType());
+ $this->assertEquals($sum, $vat->getSum());
+ }
+
+ /**
+ * Тестирует расчёт суммы НДС от суммы 100+20р и 100-20р
+ *
+ * @dataProvider providerVatsAdd
+ * @param string $type Тип НДС
+ * @param float $after_plus Результат после +20р
+ * @param float $after_minus Результат после -20р
+ * @covers \AtolOnline\Entities\Vat::addSum
+ * @covers \AtolOnline\Entities\Vat::getCalculated
+ * @throws InvalidEnumValueException
+ */
+ public function testVatAdd(string $type, float $after_plus, float $after_minus)
+ {
+ $vat = (new Vat($type, 100))->addSum(20); // 120р
+ $this->assertEquals($after_plus, $vat->getCalculated());
+ $vat->addSum(-20); // 100р
+ $this->assertEquals($after_minus, $vat->getCalculated());
+ }
+}
diff --git a/tests/AtolOnline/Tests/HelpersTest.php b/tests/AtolOnline/Tests/HelpersTest.php
new file mode 100644
index 0000000..79e90c9
--- /dev/null
+++ b/tests/AtolOnline/Tests/HelpersTest.php
@@ -0,0 +1,123 @@
+>
+ */
+ public function providerKopeksToRubles(): array
+ {
+ return [
+ [null, 0],
+ [0, 0],
+ [1, 0.01],
+ [12, 0.12],
+ [123, 1.23],
+ [1234, 12.34],
+ [12345, 123.45],
+ [-1, 0.01],
+ [-12, 0.12],
+ [-123, 1.23],
+ [-1234, 12.34],
+ [-12345, 123.45],
+ ];
+ }
+
+ /**
+ * Провайдер рублей для перевода в копейки
+ *
+ * @return array>
+ */
+ public function providerRublesToKopeks(): array
+ {
+ return [
+ [null, 0],
+ [0, 0],
+ [0.01, 1],
+ [0.12, 12],
+ [1.23, 123],
+ [12.34, 1234],
+ [123.45, 12345],
+ [-0.01, 1],
+ [-0.12, 12],
+ [-1.23, 123],
+ [-12.34, 1234],
+ [-123.45, 12345],
+ ];
+ }
+
+ /**
+ * Провайдер для тестирования генерации рандомной строки
+ *
+ * @return array>
+ */
+ public function providerRandomStr(): array
+ {
+ return [
+ [0, 0],
+ [1, 1],
+ [5, 5],
+ [-1, 1],
+ [-5, 5],
+ ];
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+
+ /**
+ * Тестирует перевод копеек в рубли
+ *
+ * @dataProvider providerKopeksToRubles
+ * @covers \AtolOnline\Helpers::toRub
+ */
+ public function testKopeksToRubles(?int $kopeks, float $rubles): void
+ {
+ $result = Helpers::toRub($kopeks);
+ $this->assertIsFloat($result);
+ $this->assertEquals($result, $rubles);
+ }
+
+ /**
+ * Тестирует перевод копеек в рубли
+ *
+ * @dataProvider providerRublesToKopeks
+ * @covers \AtolOnline\Helpers::toKop
+ */
+ public function testRublesToKopeks(?float $rubles, int $kopeks): void
+ {
+ $result = Helpers::toKop($rubles);
+ $this->assertIsInt($result);
+ $this->assertEquals($result, $kopeks);
+ }
+
+ /**
+ * Тестирует длину рандомной строки
+ *
+ * @param int $input
+ * @param int $output
+ * @dataProvider providerRandomStr
+ */
+ public function testRandomString(int $input, int $output): void
+ {
+ $result = Helpers::randomStr($input);
+ $this->assertIsString($result);
+ $this->assertEquals($output, strlen($result));
+ // тестировать на наличие цифр быссмысленно
+ }
+}
diff --git a/tests/BasicTestCase.php b/tests/BasicTestCase.php
deleted file mode 100644
index 829da25..0000000
--- a/tests/BasicTestCase.php
+++ /dev/null
@@ -1,63 +0,0 @@
-assertJson((string)$entity);
- return $this;
- }
-
- /**
- *
- */
- public function tearDown(): void
- {
- //parent::tearDown();
- }
-
- /**
- * Возвращает случайную строку указанной длины
- *
- * @param int $length
- * @return string
- */
- protected static function randomString($length = 8)
- {
- $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
- $string = '';
- for ($i = 0; $i < $length; $i++) {
- $string .= $characters[mt_rand(0, strlen($characters) - 1)];
- }
- return $string;
- }
-}
\ No newline at end of file
diff --git a/tests/Feature/ItemTest.php b/tests/Feature/ItemTest.php
deleted file mode 100644
index 0971910..0000000
--- a/tests/Feature/ItemTest.php
+++ /dev/null
@@ -1,160 +0,0 @@
-checkAtolEntity($item);
- $this->assertEquals('Банан', $item->getName());
- $this->assertEquals(65.99, $item->getPrice());
- $this->assertEquals(2.74, $item->getQuantity());
- $this->assertEquals('кг', $item->getMeasurementUnit());
- $this->assertEquals(VatTypes::NONE, $item->getVat()->getType());
- $this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject());
- $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod());
- }
-
- /**
- * Тестирует установку параметров через сеттеры
- *
- * @throws AtolOnline\Exceptions\AtolNameTooLongException
- * @throws AtolOnline\Exceptions\AtolPriceTooHighException
- * @throws AtolOnline\Exceptions\AtolTooManyException
- * @throws AtolOnline\Exceptions\AtolUnitTooLongException
- * @throws AtolOnline\Exceptions\AtolUserdataTooLongException
- */
- public function testSetters()
- {
- $item = new Item();
- $item->setName('Банан');
- $item->setPrice(65.99);
- $item->setQuantity(2.74);
- $item->setMeasurementUnit('кг');
- $item->setVatType(VatTypes::NONE);
- $item->setPaymentObject(PaymentObjects::COMMODITY);
- $item->setPaymentMethod(PaymentMethods::FULL_PAYMENT);
- $item->setUserData('Some user data');
- $this->checkAtolEntity($item);
- $this->assertEquals('Банан', $item->getName());
- $this->assertEquals(65.99, $item->getPrice());
- $this->assertEquals(2.74, $item->getQuantity());
- $this->assertEquals('кг', $item->getMeasurementUnit());
- $this->assertEquals(VatTypes::NONE, $item->getVat()->getType());
- $this->assertEquals(PaymentObjects::COMMODITY, $item->getPaymentObject());
- $this->assertEquals(PaymentMethods::FULL_PAYMENT, $item->getPaymentMethod());
- $this->assertEquals('Some user data', $item->getUserData());
- }
-
- /**
- * Тестирует установку ставки НДС разными путями
- *
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException
- */
- public function testSetVat()
- {
- $item = new Item();
- $item->setVatType(VatTypes::NONE);
- $this->assertEquals(VatTypes::NONE, $item->getVat()->getType());
- $item->setVatType(VatTypes::VAT20);
- $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
- }
-
- /**
- * Тестирует исключение о слишком длинном наименовании
- *
- * @throws \AtolOnline\Exceptions\AtolNameTooLongException
- */
- public function testAtolNameTooLongException()
- {
- $item = new Item();
- $this->expectException(AtolNameTooLongException::class);
- $item->setName(self::randomString(130));
- }
-
- /**
- * Тестирует исключение о слишком высоком количестве
- *
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException
- * @throws \AtolOnline\Exceptions\AtolTooManyException
- * @throws \AtolOnline\Exceptions\AtolUnitTooLongException
- */
- public function testAtolQuantityTooHighException()
- {
- $item = new Item();
- $this->expectException(AtolTooManyException::class);
- $item->setQuantity(100000.1);
- }
-
- /**
- * Тестирует исключение о слишком высокой цене
- *
- * @throws \AtolOnline\Exceptions\AtolPriceTooHighException
- */
- public function testAtolPriceTooHighException()
- {
- $item = new Item();
- $this->expectException(AtolPriceTooHighException::class);
- $item->setPrice(42949673.1);
- }
-
- /**
- * Тестирует исключение о слишком длинных польз. данных
- *
- * @throws \AtolOnline\Exceptions\AtolUserdataTooLongException
- */
- public function testAtolUserdataTooLongException()
- {
- $item = new Item();
- $this->expectException(AtolUserdataTooLongException::class);
- $item->setUserData('User data User data User data User data User data User data User data');
- }
-
- /**
- * Тестирует исключение о слишком длинной единице измерения
- *
- * @throws \AtolOnline\Exceptions\AtolUnitTooLongException
- */
- public function testAtolUnitTooLongException()
- {
- $item = new Item();
- $this->expectException(AtolUnitTooLongException::class);
- $item->setMeasurementUnit('кг кг кг кг кг кг кг кг кг ');
- }
-}
\ No newline at end of file
diff --git a/tests/Unit/ClientTest.php b/tests/Unit/ClientTest.php
deleted file mode 100644
index 0df1a53..0000000
--- a/tests/Unit/ClientTest.php
+++ /dev/null
@@ -1,103 +0,0 @@
-checkAtolEntity($customer);
- $this->assertEquals('John Doe', $customer->getName());
- $this->assertEquals('+122997365456', $customer->getPhone());
- $this->assertEquals('john@example.com', $customer->getEmail());
- $this->assertEquals('3399013928', $customer->getInn());
- }
-
- /**
- * Тестирует исключение о слишком длинном имени
- *
- * @throws \AtolOnline\Exceptions\AtolNameTooLongException
- */
- public function testAtolNameTooLongException()
- {
- $customer = new Client();
- $this->expectException(AtolNameTooLongException::class);
- $customer->setName(self::randomString(257));
- }
-
- /**
- * Тестирует исключение о слишком длинном телефоне
- *
- * @throws \AtolOnline\Exceptions\AtolPhoneTooLongException
- */
- public function testAtolPhoneTooLongException()
- {
- $customer = new Client();
- $this->expectException(AtolPhoneTooLongException::class);
- $customer->setPhone('99999999999999999999999999999999999999999999999999999999999999999999999999');
- }
-
- /**
- * Тестирует исключение о слишком длинной почте
- *
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException
- */
- public function testAtolEmailTooLongException()
- {
- $customer = new Client();
- $this->expectException(AtolEmailTooLongException::class);
- $customer->setEmail(self::randomString(65));
- }
-
- /**
- * Тестирует исключение о некорректной почте
- *
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException
- */
- public function testAtolEmailValidateException()
- {
- $customer = new Client();
- $this->expectException(AtolEmailValidateException::class);
- $customer->setEmail(self::randomString(15));
- }
-
- /**
- * Тестирует исключение о некорректной длине ИНН
- *
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
- */
- public function testAtolInnWrongLengthException()
- {
- $company = new Client();
- $this->expectException(AtolInnWrongLengthException::class);
- $company->setInn('123456789');
- $company->setInn('1234567890123');
- }
-}
\ No newline at end of file
diff --git a/tests/Unit/CompanyTest.php b/tests/Unit/CompanyTest.php
deleted file mode 100644
index 4211303..0000000
--- a/tests/Unit/CompanyTest.php
+++ /dev/null
@@ -1,91 +0,0 @@
-checkAtolEntity($company);
- $this->assertEquals(SnoTypes::OSN, $company->getSno());
- $this->assertEquals('5544332219', $company->getInn());
- $this->assertEquals('https://v4.online.atol.ru', $company->getPaymentAddress());
- $this->assertEquals('company@example.com', $company->getEmail());
- }
-
- /**
- * Тестирует исключение о некорректной длине ИНН
- *
- * @throws \AtolOnline\Exceptions\AtolInnWrongLengthException
- */
- public function testAtolInnWrongLengthException()
- {
- $company = new Company();
- $this->expectException(AtolInnWrongLengthException::class);
- $company->setInn('321');
- $company->setInn('1234567890123');
- }
-
- /**
- * Тестирует исключение о слишком длинном платёжном адресе
- *
- * @throws \AtolOnline\Exceptions\AtolPaymentAddressTooLongException
- */
- public function testAtolPaymentAddressTooLongException()
- {
- $company = new Company();
- $this->expectException(AtolPaymentAddressTooLongException::class);
- $company->setPaymentAddress(self::randomString(257));
- }
-
- /**
- * Тестирует исключение о слишком длинной почте
- *
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException
- */
- public function testAtolEmailTooLongException()
- {
- $company = new Company();
- $this->expectException(AtolEmailTooLongException::class);
- $company->setEmail(self::randomString(65));
- }
-
- /**
- * Тестирует исключение о некорректной почте
- *
- * @throws \AtolOnline\Exceptions\AtolEmailTooLongException
- * @throws \AtolOnline\Exceptions\AtolEmailValidateException
- */
- public function testAtolEmailValidateException()
- {
- $company = new Company();
- $this->expectException(AtolEmailValidateException::class);
- $company->setEmail(self::randomString(15));
- }
-}
\ No newline at end of file
diff --git a/tests/Unit/VatTest.php b/tests/Unit/VatTest.php
deleted file mode 100644
index 57f15d3..0000000
--- a/tests/Unit/VatTest.php
+++ /dev/null
@@ -1,51 +0,0 @@
-assertEquals(0, $vat->getFinalSum(), 'Test '.$vat_type.' | 1 step');
- $vat->setSum($sum);
- $this->assertEquals($expected_set, $vat->getFinalSum(), 'Test '.$vat_type.' | 2 step');
- $vat->addSum(20);
- $this->assertEquals($expected_add, $vat->getFinalSum(), 'Test '.$vat_type.' | 3 step');
- $vat->addSum(-20);
- }
-
- /**
- * Провайдер данных для тестирования разных типов ставок НДС
- *
- * @return array
- */
- public function vatProvider()
- {
- return [
- [VatTypes::NONE, 100, 0, 0],
- [VatTypes::VAT0, 100, 0, 0],
- [VatTypes::VAT10, 100, 9.09, 10.9],
- [VatTypes::VAT18, 100, 15.25, 18.3],
- ];
- }
-}
\ No newline at end of file