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