42 Commits

Author SHA1 Message Date
b9eae25d2c Мелочи по README 2022-08-13 21:59:01 +08:00
a72963a9ec Обновлены зависимости 2022-08-13 21:53:03 +08:00
6e31af95c5 Actual homepage url 2022-02-13 23:21:56 +08:00
83a32873b3 Liberapay in funding 2022-01-30 23:01:18 +08:00
9f4b3f2427 Liberapay shields in readme 2022-01-30 23:00:48 +08:00
aa390bf453 Merge pull request #19 from anthonyaxenov/dev
Актуализация README
2021-12-20 10:44:14 +08:00
d8f13e3054 Merge branch 'master' into dev 2021-12-20 10:43:07 +08:00
080c7e7247 Исправлен README 2021-12-20 10:41:46 +08:00
4bc92ac0ad Merge pull request #17 from anthonyaxenov/dev
#16 убраны лишние проверки в тестах фискализатора
2021-12-20 00:45:24 +08:00
9fa55e7c5f #16 убраны лишние проверки в тестах фискализатора 2021-12-19 23:07:03 +08:00
baf97cad20 Merge pull request #15 from anthonyaxenov/dev
v1.0.0
2021-12-19 23:00:14 +08:00
2e8099e0a4 Актуализирован readme 2021-12-19 22:58:47 +08:00
d7f3c81fac Мелкофикс gh-actions 2021-12-19 22:52:21 +08:00
e22c1cb091 Финализация оставшихся классов 2021-12-19 22:50:52 +08:00
58bc344a86 Более или менее актуальная документация 2021-12-19 22:30:21 +08:00
fdc5ab112a Переименования классов для пущей простоты 2021-12-19 22:29:53 +08:00
71d1f2900c Большие доработки по фискилизации
- у `AtolClient` теперь возможно получить последний отправленный запрос `getLastRequest()`
- у `AtolClient::auth()` удалены аргументы за ненадобностью
- улучшен `Client::jsonSerialize()`
- исправлен `Receipt::jsonSerialize()`
- у `Receipt` и `Correction` появились методы фискализации, вкусный сахарок
- удалён енам `DocumentTypes` за ненадобностью
- исправлены тесты монитора и документов
- рабочий фискализатор с получением результатов и покрытием
2021-12-18 14:45:00 +08:00
b4cc0fec53 Мелочи по конфигу composer 2021-12-18 14:09:07 +08:00
573af15bac Фиксы геттеров логина и пароля для AtolClient 2021-12-16 18:35:49 +08:00
19653776c5 Фикс оплат и ставок в данных коррекции 2021-12-16 18:33:13 +08:00
c7d07a18f1 Мелкофиксы по кодстайлу 2021-12-12 14:50:29 +08:00
464a8f0706 Более корректный порядок тестов 2021-12-12 14:36:04 +08:00
294a3ef2f3 Допокрытие ArrayAccess-методов 2021-12-12 11:09:12 +08:00
b4af189292 Обновление зависимостей 2021-12-11 15:54:42 +08:00
6787ce3ad7 Класс документа коррекции Correction с покрытием и всякая мелочёвка
- финализация Receipt + Payment
- фиксы phpdoc
2021-12-11 15:53:57 +08:00
1d6abfd475 Более симпатичные бейджики в README 2021-12-09 20:14:35 +08:00
058ce5ed3d Доработки коллекций, чека и тестов
- `EntityCollection` сильно упрощён, добавлен выброс исключений при пустом содержимом
- `Receipt::setItems(), setPayments() и setVats()` получили одинаковые проверки входящих данных
- округление в `Vat::setSum()`
- доработаны тесты коллекций
2021-12-09 20:14:35 +08:00
16d1146826 Четвёртая итерация Receipt
- 100% покрытие
- элвисы в разных сеттерах
2021-12-08 19:04:14 +08:00
703c5178f5 Минорные апдейты зависимостей 2021-12-08 16:11:39 +08:00
fdc64954f9 Третья итерация Receipt
- просчёт ставок секи в `setVats()`
- просчёт суммы чека в `setItems()`
- геттеры `getItems()` и `getVats()` возвращают пустую коллекцию, если в чеке они отсутствуют
- фикс `vats => supplier_info` в `jsonSerialize()`
- тесты поставщика, ставок, расчёта ставок и суммы чека
2021-12-08 16:01:25 +08:00
793549aaac Entity теперь имплементирует Arrayable и ArrayAccess для совместимости с иммутабельными методами коллекций 2021-12-08 15:56:07 +08:00
b57acf8b05 EntityCollection помечен для переделки 2021-12-08 15:50:06 +08:00
b39e76f312 Вторая итерация Receipt
- фикс nullable-свойств и геттеров
- проверка на пустоту в `setPayments()`, `setItems()` и `setVats()`
- часть тестов с покрытием (конструктор, агент, исключения при пустых коллекциях)
2021-12-07 20:09:12 +08:00
a34a6927d1 Небольшой рефакторинг по тестам
- `BasicTestCase::assertAtolable() => assertIsAtolable()`
- генерация тестовых объектов `Vat`, `Payment` и `Item` вынесены в `BasicTestCase`
2021-12-07 20:04:03 +08:00
1f3d5d2f3d Первая итерация Receipt 2021-12-06 20:07:17 +08:00
359264db64 Новая сущность AdditionalUserProps с покрытием для будущей поддержки в документе 2021-12-06 19:32:50 +08:00
557c76fefa Доработка коллекций и не только
- коллекция `Items` с покрытием
- вынос коллекций из `AtolOnline\Entities` в `AtolOnline\Collections`
- фикс ] в `AtolException`
- финализирован `CorrectionInfo`
- фиксы по тестам коллекций
- прочие мелочи по phpdoc
2021-12-06 16:14:19 +08:00
bf09641c8b Класс коллекции оплат Payments 2021-12-06 14:17:05 +08:00
6b5a025051 Класс коллекции ставок НДС Vats 2021-12-06 14:16:50 +08:00
3d3eba5b4e Базовый класс коллекции объектов EntityCollection 2021-12-06 14:16:34 +08:00
a5c88cd7d3 Функции проверки наследования классов вернул из Helpers в BasicTestCase и отрефакторил
- `isSameClass()`
- `checkImplementsInterfaces()`
- `checkUsesTraits()` + переписана под наследование
2021-12-06 14:15:47 +08:00
b451c7dc68 Мелочи по phpdoc 2021-12-06 14:13:48 +08:00
108 changed files with 4696 additions and 2875 deletions

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,4 @@
github: anthonyaxenov #github: anthonyaxenov
patreon: anthonyaxenov patreon: anthonyaxenov
liberapay: anthonyaxenov
custom: [ 'https://yoomoney.ru/to/41001685237530' ] custom: [ 'https://yoomoney.ru/to/41001685237530' ]

View File

@@ -6,7 +6,7 @@ on:
push: push:
branches: [ master, dev ] branches: [ master, dev ]
pull_request: pull_request:
branches: [ master, dev ] branches: [ dev ]
jobs: jobs:
Tests: Tests:

View File

@@ -1,56 +1,45 @@
# АТОЛ Онлайн # АТОЛ Онлайн
[![Latest Stable Version](http://poser.pugx.org/axenov/atol-online/v)](https://packagist.org/packages/axenov/atol-online) Библиотека для фискализации чеков по 54-ФЗ через [облачные ККТ АТОЛ](https://online.atol.ru/).
[![Latest Unstable Version](http://poser.pugx.org/axenov/atol-online/v/unstable)](https://packagist.org/packages/axenov/atol-online)
[![Total Downloads](http://poser.pugx.org/axenov/atol-online/downloads)](https://packagist.org/packages/axenov/atol-online)
[![License](http://poser.pugx.org/axenov/atol-online/license)](https://packagist.org/packages/axenov/atol-online)
Библиотека для фискализации чеков по 54-ФЗ через [облачную ККТ АТОЛ](https://online.atol.ru/). [![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)
[![Stable Version](https://img.shields.io/packagist/v/axenov/atol-online?label=stable)](https://packagist.org/packages/axenov/atol-online)
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/axenov/atol-online?color=%23787cb4)](https://packagist.org/packages/axenov/atol-online)
[![Liberapay](https://img.shields.io/liberapay/patrons/AnthonyAxenov.svg?logo=liberapay)](https://liberapay.com/AnthonyAxenov/donate)
[![License](https://img.shields.io/packagist/l/axenov/atol-online?color=%23369883)](LICENSE)
**[Документация](/docs/readme.md)** **[Документация](/docs/readme.md)**
---
**В ветке `dev` проводится глубокий рефакторинг, стабилизация и активная подготовка к `v1.0.0`.
Документация актуализируется постепенно.**
---
| master | [![CI](https://github.com/anthonyaxenov/atol-online/actions/workflows/ci.yml/badge.svg?branch=master)](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 | [![CI dev](https://github.com/anthonyaxenov/atol-online/actions/workflows/ci.yml/badge.svg?branch=dev)](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) |
Текущие поддерживаемые версии АТОЛ Онлайн: Текущие поддерживаемые версии АТОЛ Онлайн:
| Протокол | API | ФФД | Статус | | Протокол | API | ФФД | Статус |
|----------|-----|------|-------------| |----------|-----|------|----------------|
| v4 | 5.7 | 1.05 | Рефакторинг | | v4 | 5.8 | 1.05 | Поддерживается |
| v5 | 2.0 | 1.2 | В планах | | v5 | 2.0 | 1.2 | В планах |
## Плюшечки ## Плюшечки
* Мониторинг ККТ и ФН * Мониторинг ККТ и ФН
* Фискализация докумнетов на облачной ККТ * Фискализация документов на облачной ККТ
* Валидация данных до отправки документа на ККТ (насколько это возможно, согласно схеме) * Валидация данных до отправки документа на ККТ (насколько это возможно, согласно схеме)
* Расчёты денег в копейках * Расчёты денег в копейках
* PSR-4 автозагрузка * PSR-4 автозагрузка, покрытие настоящими тестами, fluent-setters, докблоки
<!--* Фактически полное покрытие тестами-->
## Системные требования ## Системные требования
* php8.0+ * `php v8.0` и выше
* `php-json`
* `php-curl`
* `php-mbstring`
* `php-tokenizer`
* [composer](https://getcomposer.org/) * [composer](https://getcomposer.org/)
* расширения php (скорее всего, устанавливать их отдельно не придётся):
* `php-json`
* `php-curl`
* `php-mbstring`
* `php-tokenizer`
## Начало работы ## Начало работы
### Подключение библиотеки ### Подключение библиотеки
1. Установить библиотеку пакет к проекту: 1. Подключить пакет к проекту:
```bash ```bash
composer require axenov/atol-online composer require axenov/atol-online
``` ```
@@ -69,30 +58,23 @@
```bash ```bash
composer test # обычное тестирование composer test # обычное тестирование
composer test-cov # тестирование с покрытием composer coverage # тестирование с покрытием
``` ```
После тестирования с покрытием создаётся отчёт в директории `.coverage-report` в корне репозитория. После тестирования с покрытием создаётся отчёт в директории `.coverage` в корне репозитория.
## Использование библиотеки ### Использование библиотеки
Весь исходный код находится в директории [`/src`](/src). Весь исходный код находится в директории [`/src`](/src).
Вы имеете право использовать и распространять его на условиях **[лицензии MIT](LICENSE)**.
**Комментарии phpdoc есть буквально везде. Прокомментировано вообще всё.** 1. Обращайтесь к [документации библиотеки](/docs)
2. Обращайтесь к [исходному коду](/src) и описанным докблокам
1. Обращайтесь к [документации библиотеки](/docs). 3. Обращайтесь к [тестам](/tests/AtolOnline/Tests)
2. Обращайтесь к [исходному коду](/src). 4. Используйте нормальную IDE
3. Обращайтесь к [тестам](/tests).
4. Используйте подсказки вашей IDE.
Тогда у вас не возникнет затруднений.
## Дополнительные ресурсы ## Дополнительные ресурсы
* **[Документация к библиотеке](/docs/readme.md)**
* Telegram-канал: [@atolonline_php](https://t.me/atolonline_php)
* [Документация АТОЛ Онлайн](https://online.atol.ru/lib/) * [Документация АТОЛ Онлайн](https://online.atol.ru/lib/)
## Лицензия [![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/AnthonyAxenov/donate)
Вы имеете право использовать и распространят код из этого репозитория на условиях **[лицензии MIT](LICENSE)**.

View File

@@ -27,13 +27,13 @@
"authors": [ "authors": [
{ {
"name": "Anthony Axenov", "name": "Anthony Axenov",
"homepage": "http://anthonyaxenov.ru", "homepage": "https://axenov.dev",
"email": "anthonyaxenov@gmail.com" "email": "anthonyaxenov@gmail.com"
} }
], ],
"support": { "support": {
"rss": "https://t.me/atolonline_php", "rss": "https://github.com/anthonyaxenov/atol-online/discussions/categories/announcements",
"chat": "https://t.me/+Rky7ia68fctjZmUy", "chat": "https://github.com/anthonyaxenov/atol-online/discussions",
"source": "https://github.com/anthonyaxenov/atol-online", "source": "https://github.com/anthonyaxenov/atol-online",
"issues": "https://github.com/anthonyaxenov/atol-online/issues", "issues": "https://github.com/anthonyaxenov/atol-online/issues",
"docs": "https://github.com/anthonyaxenov/atol-online/blob/master/docs/readme.md" "docs": "https://github.com/anthonyaxenov/atol-online/blob/master/docs/readme.md"
@@ -74,6 +74,10 @@
}, },
"scripts": { "scripts": {
"test": "vendor/bin/phpunit --colors=always", "test": "vendor/bin/phpunit --colors=always",
"test-cov": "php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html .coverage-report" "coverage": "php -dxdebug.mode=coverage vendor/bin/phpunit --coverage-html .coverage"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
} }
} }

543
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +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', // наименование
'john@example.com', // email
'+1/22/99*73s dsdas654 5s6', // номер телефона +122997365456
'+fasd3\qe3fs_=nac990139928czc' // номер ИНН 3399013928
);
// 2 способ - через сеттеры
$customer = (new AtolOnline\Entities\Client())
->setEmail('john@example.com')
->setInn('+fasd3\q3fs_=nac9901 3928c-c')
->setName('John Doe')
->setPhone('+1/22/99*73s dsdas654 5s6');
// либо комбинация этих способов
```
Получить установленные значения атрибутов можно через геттеры:
```php
$customer->getInn();
$customer->getEmail();
$customer->getName();
$customer->getPhone();
```
Объект класса приводится к JSON-строке автоматически или принудительно:
```php
echo $customer;
$json_string = (string)$customer;
```
Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
```php
$json_array = $customer->jsonSerialize();
```
---
[Вернуться к содержанию](readme.md)

19
docs/collection.md Normal file
View File

@@ -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)

View File

@@ -1,67 +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(
'company@example.com' // email
AtolOnline\Constants\SnoTypes::OSN, // тип СНО
'5544332219', // номер ИНН
'https://v4.online.atol.ru', // адрес места расчётов
);
// 2 способ - через сеттеры
$company
->setEmail('company@example.com')
->setInn('5544332219')
->setSno(AtolOnline\Constants\SnoTypes::USN_INCOME)
->setPaymentAddress('https://v4.online.atol.ru');
// либо комбинация этих способов
```
Получить установленные значения параметров можно через геттеры:
```php
$company->getInn();
$company->getEmail();
$company->getPaymentAddress();
$company->getSno();
```
Объект класса приводится к JSON-строке автоматически или принудительно:
```php
echo $company;
$json_string = (string)$company;
```
Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
```php
$json_array = $company->jsonSerialize();
```
---
[Вернуться к содержанию](readme.md)

View File

@@ -1,62 +0,0 @@
# Работа с данными коррекции
[Вернуться к содержанию](readme.md)
---
Объект для данных коррекции инициализируется следующим образом:
```php
$info = new AtolOnline\Entities\CorrectionInfo();
```
У объекта должны быть указаны все следующие обязательные атрибуты:
* тип коррекции (тег ФФД 1173) - все типы перечислены в классе `AtolOnline\Constants\CorrectionTypes`;
* дата документа основания для коррекции в формате `d.m.Y` (тег ФФД 1178);
* номер документа основания для коррекции (тег ФФД 1179).
Указать эти атрибуты можно двумя способами:
```php
use AtolOnline\{Entities\CorrectionInfo, Constants\CorrectionTypes};
// 1 способ - через конструктор
$info = new CorrectionInfo(
CorrectionTypes::SELF, // тип коррекции
'01.01.2019', // дата документа коррекции
'12345', // номер документа коррекции
);
// 2 способ - через сеттеры
$info = (new CorrectionInfo())
->setType(CorrectionTypes::INSTRUCTION)
->setDate('01.01.2019')
->setNumber('9999');
// либо комбинация этих способов
```
Получить установленные значения атрибутов можно через геттеры:
```php
$info->getType();
$info->getDate();
$info->getNumber();
```
Объект класса приводится к JSON-строке автоматически или принудительным приведением к `string`:
```php
echo $customer;
$json_string = (string)$customer;
```
Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
```php
$json_array = $customer->jsonSerialize();
```
---
[Вернуться к содержанию](readme.md)

View File

@@ -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()`.
Всё в рублях.
<a name='correction'></a>
## Работа с данными коррекции
Если документ создаётся с целью коррекции прихода или расхода, то он обязательно должен содержать [данные коррекции](/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)

52
docs/entity.md Normal file
View File

@@ -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)

237
docs/fiscalizing.md Normal file
View File

@@ -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);
```
<a name="testmode"></a>
## Тестовый режим
По умолчанию фискализатор создаётся для работы в тестовом режиме. Это означает, что работа с АТОЛ Онлайн 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)

View File

@@ -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();
```
<a name="array"></a>
## Массив объектов предметов расчёта
> Максимальное количество объектов в массиве - 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)

View File

@@ -1,317 +0,0 @@
# Работа с ККТ
[Вернуться к содержанию](readme.md)
---
## Доступ к ККТ
Для работы с облачной ККТ необходимы следующие параметры:
* логин;
* пароль;
* код группы.
Чтоы получить их, нужно:
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\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), который будет передаваться в документах через эту ККТ.
<a name='testmode'></a>
## Тестовый режим
На самом деле, в АТОЛ Онлайн нет понятия *тестовая операция* или чего-то в этом духе.
АТОЛ предоставляет нам отдельную тестовую среду (ККТ).
[Её настройки](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)

View File

@@ -1,6 +1,6 @@
# Мониторинг ККТ # Мониторинг ККТ
[Вернуться к содержанию](readme.md) [Вернуться к содержанию](readme.md#toc)
--- ---
@@ -12,13 +12,13 @@
```php ```php
// можно передать параметры подключения в конструктор // можно передать параметры подключения в конструктор
$monitor = new AtolOnline\Api\KktMonitor( $monitor = new AtolOnline\Api\Monitor(
login: 'mylogin', login: 'mylogin',
password: 'qwerty' password: 'qwerty'
); );
// можно - отдельными сеттерами // можно - отдельными сеттерами
$monitor = new AtolOnline\Api\KktMonitor(); $monitor = new AtolOnline\Api\Monitor();
->setLogin($credentials['login']) ->setLogin($credentials['login'])
->setPassword($credentials['password']); ->setPassword($credentials['password']);
``` ```
@@ -30,7 +30,7 @@ $monitor = new AtolOnline\Api\KktMonitor();
```php ```php
// передачей в конструктор `false` первым параметром: // передачей в конструктор `false` первым параметром:
$monitor = new AtolOnline\Api\KktMonitor(false, /*...*/); $monitor = new AtolOnline\Api\Monitor(false, /*...*/);
// или отдельным сеттером // или отдельным сеттером
$monitor->setTestMode(false); $monitor->setTestMode(false);
@@ -107,10 +107,12 @@ $kkt = $monitor->getOne($kkts->first()->serialNumber);
Класс `AtolOnline\Api\KktMonitor` расширяет абстрактный класс `AtolOnline\Api\AtolClient`. Класс `AtolOnline\Api\KktMonitor` расширяет абстрактный класс `AtolOnline\Api\AtolClient`.
Это значит, что последний ответ от API АТОЛ всегда сохраняется объектом класса `AtolOnline\Api\KktResponse`. К нему Это значит, что последний ответ от API АТОЛ всегда сохраняется объектом класса `AtolOnline\Api\AtolReponse`. К нему
можно обратиться через метод `AtolOnline\Api\KktMonitor::getResponse()`, независимо от того, что возвращают другие можно обратиться через метод `AtolOnline\Api\KktMonitor::getResponse()`, независимо от того, что возвращают другие
методы монитора. методы монитора.
--- ---
[Вернуться к содержанию](readme.md) Читай также: [Обработка ответа API](response.md)
[Вернуться к содержанию](readme.md#toc)

View File

@@ -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();
```
<a name="array"></a>
## Массив объектов оплат
> Максимальное количество объектов в массиве - 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)

View File

@@ -1,26 +1,42 @@
# Документация к библиотеке # Документация к библиотеке
<a id="toc"></a>
## Содержание
* [Общий алгоритм](#getstarted)
* [Сущность](entity.md)
* [Коллекция сущностей](collection.md)
* [Мониторинг ККТ](monitoring.md)
* [Фискализация документа](fiscalizing.md)
* [Обработка ответа API](response.md)
Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.
<a id="getstarted"></a>
## Общий алгоритм ## Общий алгоритм
1. Задать настройки ККТ 1. Создать документ `AtolOnline\Entities\Receipt` или `AtolOnline\Entities\Correction`, добавив в него все необходимые
2. Собрать данные о покупателе данные
3. Собрать данные о продавце 2. Отправить документ на регистрацию:
4. Собрать данные о предметах расчёта (товары, услуги и пр.) 2.1. *Необязательно:* при отправке задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа;
5. Создать документ, добавив в него покупателя, продавца и предметы расчёта 2.2. *Необязательно:* при отправке задать `external_id`, чтобы присвоить свой уникальный идентификатор документа;
6. Отправить документ на регистрацию: 3. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации.
6.1. *Необязательно:* задать `callback_url`, на который АТОЛ отправит HTTP POST о состоянии документа.
7. Запомнить `uuid` из пришедшего ответа, поскольку он пригодится для последующей проверки статуса фискализации.
> Если с документом был передан `callback_url`, то ответ придёт на этот самый URL. > Если с документом был передан `callback_url`, то ответ придёт на этот самый URL.
Если с документом **НЕ** был передан `callback_url` **либо** callback от АТОЛа не пришёл в течение 300 секунд (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа. > Он должен быть обработан вашим сервисом в соответствии с бизнес-процессом.
8. Проверить состояние документа:
8.1. взять `uuid` ответа, полученного на запрос фискализации; > Если с документом **не был** передан `callback_url` **либо** callback от АТОЛа не пришёл в течение 300 секунд
8.2. отправить его в запросе состояния документа. > (5 минут), нужно запрашивать вручную по `uuid`, пришедшему от АТОЛа в ответ на регистрацию документа.
4. Проверить состояние документа:
4.1. взять `uuid` ответа, полученного на запрос фискализации;
4.2. отправить его в запросе состояния документа.
> Данные о документе можно получить только в течение 32 суток после успешной фискализации. > Данные о документе можно получить только в течение 32 суток после успешной фискализации.
В зависимости от специфики бизнеса, в документах можно/нужно передавать также и другую информацию. Подробности в В зависимости от специфики бизнеса, в документах можно/нужно передавать разную информацию. Подробности в документации
документациях и исходниках. АТОЛ Онлайн и исходниках библиотеки.
### Об отправке электронного чека покупателю ## Об отправке электронного чека покупателю
После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в После успешной фискализации документа покупатель автоматически получит уведомление **от ОФД**, который используется в
связке с вашей ККТ: связке с вашей ККТ:
@@ -31,19 +47,3 @@
* если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД). * если на стороне ОФД необходима и подключена услуга СМС-информирования (уточняйте подробности о своего ОФД).
> Если заданы email и телефон, то ОФД отдаёт приоритет email. > Если заданы email и телефон, то ОФД отдаёт приоритет email.
## Подготовка документа
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)
9. [Мониторинг ККТ](monitoring.md)
---
Если вы нашли опечатку или какое-то несоответствие — делайте pull-request.

57
docs/response.md Normal file
View File

@@ -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)

View File

@@ -1,128 +0,0 @@
# Работа со ставками НДС
[Вернуться к содержанию](readme.md)
---
## Один объект
Объект ставки НДС инициализируется следующим образом:
```php
use AtolOnline\Entities\Vat;
use AtolOnline\Enums\VatTypes;
$vat = new Vat(
VatTypes::VAT10, // тип ставки
123.45 // сумма в рублях, от которой считать ставку
);
```
Для типа и суммы имеются отдельные сеттеры:
```php
$vat->setType(VatTypes::VAT20)
->setSum(100.15); // 123.45 заменится на 100.15
```
Общую сумму, из которой расчитывается размер налога, можно увеличить, используя метод `addSum()`. Указанная в рублях
сумма увеличится на указанные рубли. Для уменьшения суммы следует передать отрицательное число.
```php
$vat->addSum(40) // 100.15 + 40 = 140.15р
->addSum(-15); // 140.15 - 15 = 125.15р
```
Получить установленную сумму можно через геттер `getSum()`:
```php
$vat->getSum(); // 125.15р
```
Размер налога по ставке высчитывается из этой общей суммы. Не смотря на то, что геттер и сеттер работают с рублями, **
расчёты производятся в копейках**. Сделать это можно через `getCalculated()`:
```php
$vat->getCalculated();
// для примера выше это 20% от 125.15р = 25.03р
```
Разберём комплексный пример изменения типа ставки и расчётной суммы:
Объект класса приводится к JSON-строке автоматически или принудительно:
```php
echo $vat;
$json_string = (string)$vat;
```
Чтобы получить те же данные в виде массива, нужно вызвать метод `jsonSerialize()`:
```php
$json_array = $vat->jsonSerialize();
```
<a name="array"></a>
## Массив объектов ставок НДС
> Максимальное количество в массиве - 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)

View File

@@ -12,8 +12,17 @@
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
colors="true"> colors="true">
<testsuites> <testsuites>
<testsuite name="All"> <testsuite name="Helpers">
<directory suffix="Test.php">./tests</directory> <file>tests/AtolOnline/Tests/HelpersTest.php</file>
</testsuite>
<testsuite name="Entities">
<directory>tests/AtolOnline/Tests/Entities</directory>
</testsuite>
<testsuite name="Collections">
<directory>tests/AtolOnline/Tests/Collections</directory>
</testsuite>
<testsuite name="Api">
<directory>tests/AtolOnline/Tests/Api</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>

View File

@@ -11,21 +11,34 @@ declare(strict_types = 1);
namespace AtolOnline\Api; namespace AtolOnline\Api;
use AtolOnline\{ use AtolOnline\Constants\Constraints;
Constants\Constraints, use AtolOnline\Exceptions\{
Exceptions\AuthFailedException, AuthFailedException,
Exceptions\EmptyLoginException, EmptyLoginException,
Exceptions\EmptyPasswordException, EmptyPasswordException,
Exceptions\TooLongLoginException, TooLongLoginException,
Exceptions\TooLongPasswordException}; TooLongPasswordException};
use GuzzleHttp\Client; use AtolOnline\TestEnvParams;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\{
Client,
Exception\GuzzleException};
use JetBrains\PhpStorm\Pure;
/** /**
* Класс для подключения АТОЛ Онлайн API * Класс для подключения АТОЛ Онлайн API
*/ */
abstract class AtolClient abstract class AtolClient
{ {
/**
* @var array Последний запрос к серверу АТОЛ
*/
protected array $request;
/**
* @var AtolResponse|null Последний ответ сервера АТОЛ
*/
protected ?AtolResponse $response;
/** /**
* @var bool Флаг тестового режима * @var bool Флаг тестового режима
*/ */
@@ -51,11 +64,6 @@ abstract class AtolClient
*/ */
private ?string $token = null; private ?string $token = null;
/**
* @var KktResponse|null Последний ответ сервера АТОЛ
*/
private ?KktResponse $response;
/** /**
* Конструктор * Конструктор
* *
@@ -75,14 +83,36 @@ abstract class AtolClient
?string $password = null, ?string $password = null,
array $config = [] array $config = []
) { ) {
$this->http = new Client(array_merge($config, [ $this->http = new Client(
array_merge($config, [
'http_errors' => $config['http_errors'] ?? false, 'http_errors' => $config['http_errors'] ?? false,
])); ])
);
$this->setTestMode($test_mode); $this->setTestMode($test_mode);
!is_null($login) && $this->setLogin($login); !is_null($login) && $this->setLogin($login);
!is_null($password) && $this->setPassword($password); !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;
}
/** /**
* Возвращает установленный флаг тестового режима * Возвращает установленный флаг тестового режима
* *
@@ -127,24 +157,17 @@ abstract class AtolClient
return $this; return $this;
} }
/**
* Возвращает последний ответ сервера
*
* @return KktResponse|null
*/
public function getResponse(): ?KktResponse
{
return $this->response;
}
/** /**
* Возвращает логин доступа к API * Возвращает логин доступа к API
* *
* @return string|null * @return string|null
*/ */
#[Pure]
public function getLogin(): ?string public function getLogin(): ?string
{ {
return $this->login; return $this->isTestMode()
? TestEnvParams::FFD105()['login']
: $this->login;
} }
/** /**
@@ -172,9 +195,12 @@ abstract class AtolClient
* *
* @return string|null * @return string|null
*/ */
#[Pure]
public function getPassword(): ?string public function getPassword(): ?string
{ {
return $this->password; return $this->isTestMode()
? TestEnvParams::FFD105()['password']
: $this->password;
} }
/** /**
@@ -201,6 +227,7 @@ abstract class AtolClient
* *
* @return array * @return array
*/ */
#[Pure]
private function getHeaders(): array private function getHeaders(): array
{ {
$headers['Content-type'] = 'application/json; charset=utf-8'; $headers['Content-type'] = 'application/json; charset=utf-8';
@@ -236,7 +263,7 @@ abstract class AtolClient
'login' => $this->getLogin() ?? throw new EmptyLoginException(), 'login' => $this->getLogin() ?? throw new EmptyLoginException(),
'pass' => $this->getPassword() ?? throw new EmptyPasswordException(), 'pass' => $this->getPassword() ?? throw new EmptyPasswordException(),
]); ]);
if (!$result->isValid() || !$result->getContent()->token) { if (!$result->isSuccessful() || !$result->getContent()->token) {
throw new AuthFailedException($result); throw new AuthFailedException($result);
} }
return $result->getContent()?->token; return $result->getContent()?->token;
@@ -249,7 +276,7 @@ abstract class AtolClient
* @param string $url URL * @param string $url URL
* @param array|null $data Данные для передачи * @param array|null $data Данные для передачи
* @param array|null $options Параметры Guzzle * @param array|null $options Параметры Guzzle
* @return KktResponse * @return AtolResponse
* @throws GuzzleException * @throws GuzzleException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html * @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/ */
@@ -258,40 +285,33 @@ abstract class AtolClient
string $url, string $url,
?array $data = null, ?array $data = null,
?array $options = null ?array $options = null
): KktResponse { ): AtolResponse {
$http_method = strtoupper(trim($http_method)); $http_method = strtoupper(trim($http_method));
$options['headers'] = array_merge($this->getHeaders(), $options['headers'] ?? []); $options['headers'] = array_merge($this->getHeaders(), $options['headers'] ?? []);
if ($http_method != 'GET') { $http_method != 'GET' && $options['json'] = $data;
$options['json'] = $data; $this->request = array_merge([
} 'method' => $http_method,
'url' => $url,
], $options);
$response = $this->http->request($http_method, $url, $options); $response = $this->http->request($http_method, $url, $options);
return $this->response = new KktResponse($response); return $this->response = new AtolResponse($response);
} }
/** /**
* Выполняет авторизацию на сервере АТОЛ * Выполняет авторизацию на сервере АТОЛ
* Авторизация выполнится только если неизвестен токен
* *
* Авторизация выолнится только если неизвестен токен
*
* @param string|null $login
* @param string|null $password
* @return bool * @return bool
* @throws AuthFailedException * @throws AuthFailedException
* @throws TooLongLoginException
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
* @throws TooLongPasswordException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function auth(?string $login = null, ?string $password = null): bool public function auth(): bool
{ {
if (empty($this->getToken())) { if (empty($this->getToken()) && $token = $this->doAuth()) {
$login && $this->setLogin($login);
$password && $this->setPassword($password);
if ($token = $this->doAuth()) {
$this->setToken($token); $this->setToken($token);
} }
}
return !empty($this->getToken()); return !empty($this->getToken());
} }

View File

@@ -7,13 +7,17 @@
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
/** @noinspection PhpMultipleClassDeclarationsInspection */
declare(strict_types = 1); declare(strict_types = 1);
namespace AtolOnline\Api; namespace AtolOnline\Api;
use JetBrains\PhpStorm\{
ArrayShape,
Pure};
use JsonSerializable; use JsonSerializable;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use stdClass;
use Stringable; use Stringable;
/** /**
@@ -22,7 +26,7 @@ use Stringable;
* @property mixed $error * @property mixed $error
* @package AtolOnline\Api * @package AtolOnline\Api
*/ */
class KktResponse implements JsonSerializable, Stringable final class AtolResponse implements JsonSerializable, Stringable
{ {
/** /**
* @var int Код ответа сервера * @var int Код ответа сервера
@@ -30,9 +34,9 @@ class KktResponse implements JsonSerializable, Stringable
protected int $code; protected int $code;
/** /**
* @var stdClass|array|null Содержимое ответа сервера * @var object|array|null Содержимое ответа сервера
*/ */
protected stdClass|array|null $content; protected object|array|null $content;
/** /**
* @var array Заголовки ответа * @var array Заголовки ответа
@@ -67,6 +71,7 @@ class KktResponse implements JsonSerializable, Stringable
* @param $name * @param $name
* @return mixed * @return mixed
*/ */
#[Pure]
public function __get($name): mixed public function __get($name): mixed
{ {
return $this->getContent()?->$name; return $this->getContent()?->$name;
@@ -97,7 +102,8 @@ class KktResponse implements JsonSerializable, Stringable
* *
* @return bool * @return bool
*/ */
public function isValid(): bool #[Pure]
public function isSuccessful(): bool
{ {
return !empty($this->getCode()) return !empty($this->getCode())
&& !empty($this->getContent()) && !empty($this->getContent())
@@ -116,6 +122,12 @@ class KktResponse implements JsonSerializable, Stringable
/** /**
* @inheritDoc * @inheritDoc
*/ */
#[ArrayShape([
'code' => 'int',
'headers' => 'array|\string[][]',
'body' => 'mixed',
]
)]
public function jsonSerialize(): array public function jsonSerialize(): array
{ {
return [ return [

378
src/Api/Fiscalizer.php Normal file
View File

@@ -0,0 +1,378 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Api;
use AtolOnline\{
Constants\Constraints,
TestEnvParams};
use AtolOnline\Entities\{
Correction,
Receipt};
use AtolOnline\Exceptions\{
AuthFailedException,
EmptyGroupException,
EmptyLoginException,
EmptyPasswordException,
InvalidCallbackUrlException,
InvalidEntityInCollectionException,
InvalidInnLengthException,
InvalidPaymentAddressException,
InvalidUuidException,
TooLongCallbackUrlException,
TooLongLoginException,
TooLongPasswordException,
TooLongPaymentAddressException};
use GuzzleHttp\Exception\GuzzleException;
use JetBrains\PhpStorm\Pure;
use Ramsey\Uuid\Uuid;
/**
* Класс фискализатора для регистрации документов на ККТ
*/
final class Fiscalizer extends AtolClient
{
/**
* @var string|null Группа ККТ
*/
private ?string $group = null;
/**
* @var string|null URL для приёма POST-запроса от API АТОЛ с результатом регистрации документа
*/
private ?string $callback_url = null;
/**
* Конструктор
*
* @param bool $test_mode
* @param string|null $login
* @param string|null $password
* @param string|null $group
* @param array $config
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
* @throws EmptyGroupException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/
public function __construct(
bool $test_mode = true,
?string $login = null,
?string $password = null,
?string $group = null,
array $config = []
) {
parent::__construct($test_mode, $login, $password, $config);
!is_null($group) && $this->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);
}
}

View File

@@ -1,378 +0,0 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Api;
use AtolOnline\{
Constants\Constraints,
Entities\Company,
Entities\Document,
Exceptions\AuthFailedException,
Exceptions\EmptyCorrectionInfoException,
Exceptions\EmptyLoginException,
Exceptions\EmptyPasswordException,
Exceptions\InvalidCallbackUrlException,
Exceptions\InvalidDocumentTypeException,
Exceptions\InvalidInnLengthException,
Exceptions\InvalidUuidException,
Exceptions\TooLongCallbackUrlException,
Exceptions\TooLongLoginException,
Exceptions\TooLongPasswordException,
Exceptions\TooLongPaymentAddressException,
Exceptions\TooManyItemsException,
Exceptions\TooManyVatsException,
TestEnvParams
};
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Ramsey\Uuid\Uuid;
/**
* Класс для регистрации документов на ККТ
*/
class KktFiscalizer extends AtolClient
{
/**
* @var string|null Группа ККТ
*/
private ?string $group = null;
/**
* @var string|null URL для приёма POST-запроса от API АТОЛ с результатом регистрации документа
*/
private ?string $callback_url = null;
/**
* Конструктор
*
* @param bool $test_mode
* @param string|null $login
* @param string|null $password
* @param string|null $group
* @param array $config
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws TooLongLoginException
* @throws TooLongPasswordException
* @see https://guzzle.readthedocs.io/en/latest/request-options.html
*/
public function __construct(
bool $test_mode = true,
?string $login = null,
?string $password = null,
?string $group = null,
array $config = []
) {
parent::__construct($test_mode, $login, $password, $config);
!is_null($group) && $this->setGroup($group);
}
/**
* Устанавливает группу доступа к ККТ
*
* @param string $group
* @return $this
*/
public function setGroup(string $group): self
{
// критерии к длине строки не описаны ни в схеме, ни в документации
$this->group = $group;
return $this;
}
/**
* Возвращает группу доступа к ККТ в соответствии с флагом тестового режима
*
* @return string|null
*/
public function getGroup(): ?string
{
return $this->group;
}
/**
* Устанавливает URL для приёма колбеков
*
* @param string $url
* @return $this
* @throws TooLongCallbackUrlException
* @throws InvalidCallbackUrlException
*/
public function setCallbackUrl(string $url): self
{
if (mb_strlen($url) > Constraints::MAX_LENGTH_CALLBACK_URL) {
throw new TooLongCallbackUrlException($url, Constraints::MAX_LENGTH_CALLBACK_URL);
} elseif (!preg_match(Constraints::PATTERN_CALLBACK_URL, $url)) {
throw new InvalidCallbackUrlException('Callback URL not matches with pattern');
}
$this->callback_url = $url;
return $this;
}
/**
* Возвращает URL для приёма колбеков
*
* @return string
*/
public function getCallbackUrl(): string
{
return $this->callback_url;
}
/**
* Регистрирует документ прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyCorrectionInfoException
* @throws InvalidInnLengthException
* @throws TooLongPaymentAddressException
* @throws InvalidDocumentTypeException
* @throws GuzzleException
*/
public function sell(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Некорректная операция над документом коррекции');
}
return $this->registerDocument('sell', 'receipt', $document, $external_id);
}
/**
* Регистрирует документ возврата прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyCorrectionInfoException
* @throws InvalidInnLengthException
* @throws TooLongPaymentAddressException
* @throws TooManyVatsException
* @throws InvalidDocumentTypeException
* @throws GuzzleException
*/
public function sellRefund(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('sell_refund', 'receipt', $document->clearVats(), $external_id);
}
/**
* Регистрирует документ коррекции прихода
*
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyCorrectionInfoException
* @throws InvalidInnLengthException
* @throws TooLongPaymentAddressException
* @throws TooManyItemsException
* @throws InvalidDocumentTypeException
* @throws GuzzleException
*/
public function sellCorrection(Document $document, ?string $external_id = null): KktResponse
{
if (!$document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('sell_correction', 'correction', $document, $external_id);
}
/**
* Регистрирует документ расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyCorrectionInfoException
* @throws InvalidInnLengthException
* @throws TooLongPaymentAddressException
* @throws InvalidDocumentTypeException
* @throws GuzzleException
*/
public function buy(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('buy', 'receipt', $document, $external_id);
}
/**
* Регистрирует документ возврата расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyCorrectionInfoException
* @throws InvalidInnLengthException
* @throws TooLongPaymentAddressException
* @throws TooManyVatsException
* @throws InvalidDocumentTypeException
* @throws GuzzleException
*/
public function buyRefund(Document $document, ?string $external_id = null): KktResponse
{
if ($document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException('Invalid operation on correction document');
}
return $this->registerDocument('buy_refund', 'receipt', $document->clearVats(), $external_id);
}
/**
* Регистрирует документ коррекции расхода
*
* @param Document $document
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws EmptyCorrectionInfoException В документе отсутствуют данные коррекции
* @throws InvalidInnLengthException Некорректная длтина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws TooManyItemsException Слишком много предметов расчёта
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws GuzzleException
*/
public function buyCorrection(Document $document, ?string $external_id = null): KktResponse
{
if (!$document->getCorrectionInfo()) {
throw new EmptyCorrectionInfoException();
}
$document->setClient(null)->setItems([]);
return $this->registerDocument('buy_correction', 'correction', $document, $external_id);
}
/**
* Проверяет статус чека на ККТ один раз
*
* @param string $uuid UUID регистрации
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidUuidException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function getDocumentStatus(string $uuid): KktResponse
{
$uuid = trim($uuid);
if (!Uuid::isValid($uuid)) {
throw new InvalidUuidException($uuid);
}
$this->auth();
return $this->sendRequest('GET', 'report/' . $uuid);
}
/**
* Проверяет статус чека на ККТ нужное количество раз с указанным интервалом.
* Вернёт результат как только при очередной проверке сменится статус регистрации документа.
*
* @param string $uuid UUID регистрации
* @param int $retry_count Количество попыток
* @param int $timeout Таймаут в секундах между попытками
* @return KktResponse
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException
* @throws InvalidUuidException
* @throws TooLongLoginException
* @throws TooLongPasswordException
*/
public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1): KktResponse
{
$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;
}
/**
* Отправляет документ на регистрацию
*
* @param string $api_method Метод API
* @param string $type Тип документа: receipt, correction
* @param Document $document Объект документа
* @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID)
* @return KktResponse
* @throws AuthFailedException Ошибка авторизации
* @throws InvalidDocumentTypeException Некорректный тип документа
* @throws InvalidInnLengthException Некорректная длина ИНН
* @throws TooLongPaymentAddressException Слишком длинный адрес места расчётов
* @throws GuzzleException
* @throws Exception
*/
protected function registerDocument(
string $api_method,
string $type,
Document $document,
?string $external_id = null
): KktResponse {
$type = trim($type);
if (!in_array($type, ['receipt', 'correction'])) {
throw new InvalidDocumentTypeException($type);
}
$this->auth();
if ($this->isTestMode()) {
$document->setCompany(new Company(
'test@example.com',
TestEnvParams::FFD105()['sno'],
TestEnvParams::FFD105()['inn'],
TestEnvParams::FFD105()['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->sendRequest('POST', trim($api_method), $data);
}
/**
* @inheritDoc
*/
protected function getAuthEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/possystem/v1/getToken'
: 'https://online.atol.ru/possystem/v1/getToken';
}
/**
* @inheritDoc
*/
protected function getMainEndpoint(): string
{
return $this->isTestMode()
? 'https://testonline.atol.ru/possystem/v4/'
: 'https://online.atol.ru/possystem/v4/';
}
}

View File

@@ -12,21 +12,27 @@ declare(strict_types = 1);
namespace AtolOnline\Api; namespace AtolOnline\Api;
use AtolOnline\Entities\Kkt; use AtolOnline\Entities\Kkt;
use AtolOnline\Exceptions\EmptyMonitorDataException; use AtolOnline\Exceptions\{
use AtolOnline\Exceptions\NotEnoughMonitorDataException; AuthFailedException,
EmptyLoginException,
EmptyMonitorDataException,
EmptyPasswordException,
NotEnoughMonitorDataException};
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use JetBrains\PhpStorm\Pure;
/** /**
* Класс для мониторинга ККТ * Класс для мониторинга ККТ
* *
* @see https://online.atol.ru/files/API_service_information.pdf Документация * @see https://online.atol.ru/files/API_service_information.pdf Документация
*/ */
class KktMonitor extends AtolClient final class Monitor extends AtolClient
{ {
/** /**
* @inheritDoc * @inheritDoc
*/ */
#[Pure]
protected function getAuthEndpoint(): string protected function getAuthEndpoint(): string
{ {
return $this->isTestMode() return $this->isTestMode()
@@ -37,6 +43,7 @@ class KktMonitor extends AtolClient
/** /**
* @inheritDoc * @inheritDoc
*/ */
#[Pure]
protected function getMainEndpoint(): string protected function getMainEndpoint(): string
{ {
return $this->isTestMode() return $this->isTestMode()
@@ -49,16 +56,21 @@ class KktMonitor extends AtolClient
* *
* @param int|null $limit * @param int|null $limit
* @param int|null $offset * @param int|null $offset
* @return KktResponse * @return AtolResponse|null
* @throws GuzzleException * @throws GuzzleException
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9 * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
*/ */
protected function fetchAll(?int $limit = null, ?int $offset = null): KktResponse protected function fetchAll(?int $limit = null, ?int $offset = null): ?AtolResponse
{ {
$params = []; $params = [];
!is_null($limit) && $params['limit'] = $limit; !is_null($limit) && $params['limit'] = $limit;
!is_null($offset) && $params['offset'] = $offset; !is_null($offset) && $params['offset'] = $offset;
return $this->sendRequest('GET', self::getUrlToMethod('cash-registers'), $params); return $this->auth()
? $this->sendRequest('GET', self::getUrlToMethod('cash-registers'), $params)
: null;
} }
/** /**
@@ -67,6 +79,9 @@ class KktMonitor extends AtolClient
* @param int|null $limit * @param int|null $limit
* @param int|null $offset * @param int|null $offset
* @return Collection * @return Collection
* @throws AuthFailedException
* @throws EmptyLoginException
* @throws EmptyPasswordException
* @throws GuzzleException * @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9 * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9
*/ */
@@ -80,11 +95,11 @@ class KktMonitor extends AtolClient
* Получает от API информацию о конкретной ККТ по её серийному номеру * Получает от API информацию о конкретной ККТ по её серийному номеру
* *
* @param string $serial_number * @param string $serial_number
* @return KktResponse * @return AtolResponse
* @throws GuzzleException * @throws GuzzleException
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11 * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
*/ */
protected function fetchOne(string $serial_number): KktResponse protected function fetchOne(string $serial_number): AtolResponse
{ {
return $this->sendRequest( return $this->sendRequest(
'GET', 'GET',
@@ -100,7 +115,6 @@ class KktMonitor extends AtolClient
/** /**
* Возвращает информацию о конкретной ККТ по её серийному номеру * Возвращает информацию о конкретной ККТ по её серийному номеру
* *
* @todo кастовать к отдельному классу со своими геттерами
* @param string $serial_number * @param string $serial_number
* @return Kkt * @return Kkt
* @throws GuzzleException * @throws GuzzleException

View File

@@ -0,0 +1,68 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Collections;
use AtolOnline\Exceptions\InvalidEntityInCollectionException;
use Illuminate\Support\Collection;
/**
* Абстрактное описание коллекции любых сущностей
*/
abstract class EntityCollection extends Collection
{
/**
* @inheritDoc
* @throws InvalidEntityInCollectionException
*/
public function jsonSerialize(): array
{
$this->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));
}
}

41
src/Collections/Items.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constants\Constraints;
use AtolOnline\Entities\Item;
use AtolOnline\Exceptions\EmptyItemsException;
use AtolOnline\Exceptions\TooManyItemsException;
/**
* Класс, описывающий коллекцию предметов расчёта для документа
*/
final class Items extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Item::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_ITEMS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyItemsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyItemsException::class;
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constants\Constraints;
use AtolOnline\Entities\Payment;
use AtolOnline\Exceptions\EmptyPaymentsException;
use AtolOnline\Exceptions\TooManyPaymentsException;
/**
* Класс, описывающий коллекцию оплат для документа
*/
final class Payments extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Payment::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_PAYMENTS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyPaymentsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyPaymentsException::class;
}

41
src/Collections/Vats.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Collections;
use AtolOnline\Constants\Constraints;
use AtolOnline\Entities\Vat;
use AtolOnline\Exceptions\EmptyVatsException;
use AtolOnline\Exceptions\TooManyVatsException;
/**
* Класс, описывающий коллекцию ставок НДС для документа
*/
final class Vats extends EntityCollection
{
/**
* Класс объектов, находящихся в коллекции
*/
protected const ENTITY_CLASS = Vat::class;
/**
* Максмальное количество объектов в коллекции
*/
protected const MAX_COUNT = Constraints::MAX_COUNT_DOC_VATS;
/**
* Класс исключения для выброса при пустой коллекции
*/
protected const EMPTY_EXCEPTION_CLASS = EmptyVatsException::class;
/**
* Класс-наследник TooManyException для выброса при превышении количества
*/
protected const TOO_MANY_EXCEPTION_CLASS = TooManyVatsException::class;
}

View File

@@ -43,24 +43,28 @@ final class Constraints
/** /**
* Максимальная длина наименования покупателя (1227) * Максимальная длина наименования покупателя (1227)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/ */
const MAX_LENGTH_CLIENT_NAME = 256; const MAX_LENGTH_CLIENT_NAME = 256;
/** /**
* Максимальная длина наименования предмета расчёта (1030) * Максимальная длина наименования предмета расчёта (1030)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_LENGTH_ITEM_NAME = 128; const MAX_LENGTH_ITEM_NAME = 128;
/** /**
* Максимальная цена за единицу предмета расчёта (1079) * Максимальная цена за единицу предмета расчёта (1079)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_COUNT_ITEM_PRICE = 42949672.95; const MAX_COUNT_ITEM_PRICE = 42949672.95;
/** /**
* Максимальное количество (вес) единицы предмета расчёта (1023) * Максимальное количество (вес) единицы предмета расчёта (1023)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_COUNT_ITEM_QUANTITY = 99999.999; const MAX_COUNT_ITEM_QUANTITY = 99999.999;
@@ -68,36 +72,42 @@ final class Constraints
/** /**
* Максимальная стоимость всех предметов расчёта в документе прихода, расхода, * Максимальная стоимость всех предметов расчёта в документе прихода, расхода,
* возврата прихода, возврата расхода (1043) * возврата прихода, возврата расхода (1043)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_COUNT_ITEM_SUM = 42949672.95; const MAX_COUNT_ITEM_SUM = 42949672.95;
/** /**
* Максимальная длина телефона или email покупателя (1008) * Максимальная длина телефона или email покупателя (1008)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/ */
const MAX_LENGTH_CLIENT_CONTACT = 64; const MAX_LENGTH_CLIENT_CONTACT = 64;
/** /**
* Длина операции для платёжного агента (1044) * Длина операции для платёжного агента (1044)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 19 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 19
*/ */
const MAX_LENGTH_PAYING_AGENT_OPERATION = 24; const MAX_LENGTH_PAYING_AGENT_OPERATION = 24;
/** /**
* Максимальное количество предметов расчёта в документе прихода, расхода, возврата прихода, возврата расхода * Максимальное количество предметов расчёта в документе прихода, расхода, возврата прихода, возврата расхода
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_COUNT_DOC_ITEMS = 100; const MAX_COUNT_DOC_ITEMS = 100;
/** /**
* Максимальная длина единицы измерения предмета расчётов * Максимальная длина единицы измерения предмета расчётов
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/ */
const MAX_LENGTH_MEASUREMENT_UNIT = 16; const MAX_LENGTH_MEASUREMENT_UNIT = 16;
/** /**
* Максимальная длина пользовательских данных для предмета расчётов (1191) * Максимальная длина пользовательских данных для предмета расчётов (1191)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 29 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 29
*/ */
const MAX_LENGTH_USER_DATA = 64; const MAX_LENGTH_USER_DATA = 64;
@@ -118,16 +128,23 @@ final class Constraints
/** /**
* Максимальное количество платежей в любом документе * Максимальное количество платежей в любом документе
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30 и 35 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30 и 35
*/ */
const MAX_COUNT_DOC_PAYMENTS = 10; const MAX_COUNT_DOC_PAYMENTS = 10;
/** /**
* Максимальное количество ставок НДС в любом документе * Максимальное количество ставок НДС в любом документе
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 31 и 36 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 31 и 36
*/ */
const MAX_COUNT_DOC_VATS = 6; const MAX_COUNT_DOC_VATS = 6;
/**
* Максимальная сумма одной оплаты
*/
const MAX_COUNT_PAYMENT_SUM = 99999.999;
/** /**
* Максимальная длина имени кассира (1021) * Максимальная длина имени кассира (1021)
* *
@@ -142,41 +159,62 @@ final class Constraints
*/ */
const MAX_LENGTH_ITEM_CODE = 32; const MAX_LENGTH_ITEM_CODE = 32;
/**
* Максимальная длина значения дополнительного реквизита чека (1192)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
const MAX_LENGTH_ADD_CHECK_PROP = 16;
/**
* Максимальная длина наименования дополнительного реквизита пользователя (1085)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
const MAX_LENGTH_ADD_USER_PROP_NAME = 64;
/**
* Максимальная длина значения дополнительного реквизита пользователя (1086)
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
const MAX_LENGTH_ADD_USER_PROP_VALUE = 256;
/** /**
* Формат даты документа коррекции * Формат даты документа коррекции
*/ */
const CORRECTION_DATE_FORMAT = 'd.m.Y'; const CORRECTION_DATE_FORMAT = 'd.m.Y';
/**
* Максимальная сумма одной оплаты
*/
const MAX_COUNT_PAYMENT_SUM = 99999.999;
/** /**
* Регулярное выражение для валидации строки ИНН * Регулярное выражение для валидации строки ИНН
* *
* @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/receipt/client/inn" * @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/receipt/client/inn"
*/ */
const PATTERN_INN = /* @lang PhpRegExp */ const PATTERN_INN
= /* @lang PhpRegExp */
'/(^[\d]{10}$)|(^[\d]{12}$)/'; '/(^[\d]{10}$)|(^[\d]{12}$)/';
/** /**
* Регулярное выражение для валидации номера телефона * Регулярное выражение для валидации номера телефона
*
* @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/definitions/phone_number" * @see https://online.atol.ru/possystem/v4/schema/sell Схема "#/definitions/phone_number"
*/ */
const PATTERN_PHONE = /* @lang PhpRegExp */ const PATTERN_PHONE
= /* @lang PhpRegExp */
'/^([^\s\\\]{0,17}|\+[^\s\\\]{1,18})$/'; '/^([^\s\\\]{0,17}|\+[^\s\\\]{1,18})$/';
/** /**
* Регулярное выражение для валидации строки Callback URL * Регулярное выражение для валидации строки Callback URL
*/ */
const PATTERN_CALLBACK_URL = /* @lang PhpRegExp */ const PATTERN_CALLBACK_URL
'/^http(s?)\:\/\/[0-9a-zA-Zа-яА-Я]' . = /* @lang PhpRegExp */
'([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-\.\?\,\'\/\\\+&=%\$#_]*)?$/'; '/^http(s?):\/\/[0-9a-zA-Zа-яА-Я]' .
'([-.\w]*[0-9a-zA-Zа-яА-Я])*(:(0-9)*)*(\/?)([a-zAZ0-9а-яА-Я\-.?,\'\/\\\+&=%\$#_]*)?$/';
/** /**
* Регулярное выражение для валидации кода страны происхождения товара * Регулярное выражение для валидации кода страны происхождения товара
*/ */
const PATTERN_OKSM_CODE = /* @lang PhpRegExp */ const PATTERN_OKSM_CODE
= /* @lang PhpRegExp */
'/^[\d]{3}$/'; '/^[\d]{3}$/';
} }

View File

@@ -226,4 +226,24 @@ final class Ffd105Tags
* Сумма НДС чека по расч. ставке 20/120 * Сумма НДС чека по расч. ставке 20/120
*/ */
const DOC_VAT_TYPE_VAT120 = 1106; const DOC_VAT_TYPE_VAT120 = 1106;
/**
* Значение дополнительного реквизита чека
*/
const DOC_ADD_CHECK_PROP_VALUE = 1192;
/**
* Дополнительный реквизит пользователя
*/
const DOC_ADD_USER_PROP = 1084;
/**
* Наименование дополнительного реквизита пользователя
*/
const DOC_ADD_USER_PROP_NAME = 1085;
/**
* Значение дополнительного реквизита пользователя
*/
const DOC_ADD_USER_PROP_VALUE = 1086;
} }

View File

@@ -0,0 +1,132 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\Constants\Constraints;
use AtolOnline\Exceptions\{
EmptyAddUserPropNameException,
EmptyAddUserPropValueException,
TooLongAddUserPropNameException,
TooLongAddUserPropValueException
};
use JetBrains\PhpStorm\{
ArrayShape,
Pure
};
/**
* Класс, описывающий дополнительный реквизит пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
final class AdditionalUserProps extends Entity
{
/**
* @var string Наименование (1085)
*/
protected string $name;
/**
* @var string Значение (1086)
*/
protected string $value;
/**
* Конструктор объекта покупателя
*
* @param string $name Наименование (1227)
* @param string $value Значение (1008)
* @throws EmptyAddUserPropNameException
* @throws EmptyAddUserPropValueException
* @throws TooLongAddUserPropNameException
* @throws TooLongAddUserPropValueException
*/
public function __construct(string $name, string $value)
{
$this->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(),
];
}
}

View File

@@ -17,12 +17,10 @@ use AtolOnline\Exceptions\{
InvalidInnLengthException, InvalidInnLengthException,
InvalidPhoneException, InvalidPhoneException,
TooLongClientNameException, TooLongClientNameException,
TooLongEmailException TooLongEmailException};
};
use AtolOnline\Traits\{ use AtolOnline\Traits\{
HasEmail, HasEmail,
HasInn HasInn};
};
use JetBrains\PhpStorm\Pure; use JetBrains\PhpStorm\Pure;
/** /**
@@ -94,7 +92,7 @@ final class Client extends Entity
throw new TooLongClientNameException($name); throw new TooLongClientNameException($name);
} }
} }
$this->name = empty($name) ? null : $name; $this->name = $name ?: null;
return $this; return $this;
} }
@@ -134,10 +132,10 @@ final class Client extends Entity
public function jsonSerialize(): array public function jsonSerialize(): array
{ {
$json = []; $json = [];
$this->getName() && $json['name'] = $this->getName(); !is_null($this->getName()) && $json['name'] = $this->getName();
$this->getEmail() && $json['email'] = $this->getEmail(); !is_null($this->getEmail()) && $json['email'] = $this->getEmail();
$this->getPhone() && $json['phone'] = $this->getPhone(); !is_null($this->getPhone()) && $json['phone'] = $this->getPhone();
$this->getInn() && $json['inn'] = $this->getInn(); !is_null($this->getInn()) && $json['inn'] = $this->getInn();
return $json; return $json;
} }
} }

View File

@@ -61,8 +61,8 @@ final class Company extends Entity
* @throws TooLongPaymentAddressException * @throws TooLongPaymentAddressException
*/ */
public function __construct( public function __construct(
string $email, string $email, //TODO сделать необязательным здесь
string $sno, string $sno, //TODO сделать необязательным здесь
string $inn, string $inn,
string $payment_address, string $payment_address,
) { ) {
@@ -131,10 +131,10 @@ final class Company extends Entity
* @throws InvalidPaymentAddressException * @throws InvalidPaymentAddressException
*/ */
#[ArrayShape([ #[ArrayShape([
'email' => "string", 'email' => 'string',
'sno' => "string", 'sno' => 'string',
'inn' => "string", 'inn' => 'string',
'payment_address' => "string", 'payment_address' => 'string',
])] ])]
public function jsonSerialize(): array public function jsonSerialize(): array
{ {

273
src/Entities/Correction.php Normal file
View File

@@ -0,0 +1,273 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Entities;
use AtolOnline\{
Api\AtolResponse,
Api\Fiscalizer,
Collections\Payments,
Collections\Vats,
Constants\Constraints};
use AtolOnline\Exceptions\{
AuthFailedException,
EmptyLoginException,
EmptyPasswordException,
InvalidEntityInCollectionException,
InvalidInnLengthException,
InvalidPaymentAddressException,
TooLongCashierException,
TooLongPaymentAddressException};
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use JetBrains\PhpStorm\ArrayShape;
/**
* Класс, описывающий документ коррекции
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35
*/
final class Correction extends Entity
{
/**
* Тип документа
*/
public const DOC_TYPE = 'correction';
/**
* @var Company Продавец
*/
protected Company $company;
/**
* @todo вынести в трейт?
* @var string|null ФИО кассира
*/
protected ?string $cashier = null;
/**
* @var CorrectionInfo Данные коррекции
*/
protected CorrectionInfo $correction_info;
/**
* @var Payments Коллекция оплат
*/
protected Payments $payments;
/**
* @var Vats Коллекция ставок НДС
*/
protected Vats $vats;
/**
* Конструктор
*
* @param Company $company
* @param CorrectionInfo $correction_info
* @param Payments $payments
* @param Vats $vats
* @throws InvalidEntityInCollectionException
* @throws Exception
*/
public function __construct(
Company $company,
CorrectionInfo $correction_info,
Payments $payments,
Vats $vats,
) {
$this->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;
}
}

View File

@@ -16,21 +16,19 @@ use AtolOnline\Enums\CorrectionTypes;
use AtolOnline\Exceptions\{ use AtolOnline\Exceptions\{
EmptyCorrectionNumberException, EmptyCorrectionNumberException,
InvalidCorrectionDateException, InvalidCorrectionDateException,
InvalidEnumValueException InvalidEnumValueException};
};
use DateTime; use DateTime;
use Exception; use Exception;
use JetBrains\PhpStorm\{ use JetBrains\PhpStorm\{
ArrayShape, ArrayShape,
Pure Pure};
};
/** /**
* Класс, описывающий данные коррекции * Класс, описывающий данные коррекции
* *
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 35
*/ */
class CorrectionInfo extends Entity final class CorrectionInfo extends Entity
{ {
/** /**
* @var string|null Тип коррекции (1173) * @var string|null Тип коррекции (1173)

View File

@@ -7,18 +7,44 @@
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE * https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/ */
/** @noinspection PhpMultipleClassDeclarationsInspection */
declare(strict_types = 1); declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use ArrayAccess;
use BadMethodCallException;
use Illuminate\Contracts\Support\Arrayable;
use JetBrains\PhpStorm\ArrayShape;
use JsonSerializable; use JsonSerializable;
use Stringable; use Stringable;
/** /**
* Абстрактное описание любой сущности, представляемой как json * Абстрактное описание любой сущности, представляемой как json
*/ */
abstract class Entity implements JsonSerializable, Stringable abstract class Entity implements JsonSerializable, Stringable, Arrayable, ArrayAccess
{ {
/**
* @inheritDoc
*/
abstract public function jsonSerialize(): array;
/**
* @inheritDoc
*/
#[ArrayShape([
'company' => "\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-структуры объекта * Возвращает строковое представление json-структуры объекта
* *
@@ -26,6 +52,42 @@ abstract class Entity implements JsonSerializable, Stringable
*/ */
public function __toString() public function __toString()
{ {
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE); 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 . ' нельзя изменять как массив. Следует использовать сеттеры.'
);
} }
} }

View File

@@ -556,8 +556,8 @@ final class Item extends Entity
if (is_string($declaration_number)) { if (is_string($declaration_number)) {
$declaration_number = trim($declaration_number); $declaration_number = trim($declaration_number);
if ( if (
mb_strlen($declaration_number) < Constraints::MIN_LENGTH_DECLARATION_NUMBER || mb_strlen($declaration_number) < Constraints::MIN_LENGTH_DECLARATION_NUMBER
mb_strlen($declaration_number) > Constraints::MAX_LENGTH_DECLARATION_NUMBER || mb_strlen($declaration_number) > Constraints::MAX_LENGTH_DECLARATION_NUMBER
) { ) {
throw new InvalidDeclarationNumberException($declaration_number); throw new InvalidDeclarationNumberException($declaration_number);
} }

View File

@@ -13,8 +13,7 @@ namespace AtolOnline\Entities;
use AtolOnline\Exceptions\{ use AtolOnline\Exceptions\{
EmptyMonitorDataException, EmptyMonitorDataException,
NotEnoughMonitorDataException NotEnoughMonitorDataException};
};
use DateTime; use DateTime;
use Exception; use Exception;
@@ -22,25 +21,26 @@ use Exception;
* Класс сущности ККТ, получаемой от монитора * Класс сущности ККТ, получаемой от монитора
* *
* @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11 * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 11
* @property string|null serialNumber Заводской номер ККТ * @property-read string|null serialNumber Заводской номер ККТ
* @property string|null registrationNumber Регистрационный номер машины (РНМ) * @property-read string|null registrationNumber Регистрационный номер машины (РНМ)
* @property string|null deviceNumber Номер автоматического устройства (внутренний идентификатор устройства) * @property-read string|null deviceNumber Номер автоматического устройства (внутренний идентификатор устройства)
* @property DateTime|string|null fiscalizationDate Дата активации (фискализации) ФН с указанием таймзоны * @property-read DateTime|string|null fiscalizationDate Дата активации (фискализации) ФН с указанием таймзоны
* @property DateTime|string|null fiscalStorageExpiration Дата замены ФН (Срок действия ФН), с указанием таймзоны * @property-read DateTime|string|null fiscalStorageExpiration Дата замены ФН (Срок действия ФН), с указанием таймзоны
* @property int|null signedDocuments Количество подписанных документов в ФН * @property-read int|null signedDocuments Количество подписанных документов в ФН
* @property float|null fiscalStoragePercentageUse Наполненость ФН в % * @property-read float|null fiscalStoragePercentageUse Наполненость ФН в %
* @property string|null fiscalStorageINN ИНН компании (указанный в ФН) * @property-read string|null fiscalStorageINN ИНН компании (указанный в ФН)
* @property string|null fiscalStorageSerialNumber Заводской (серийный) номер ФН * @property-read string|null fiscalStorageSerialNumber Заводской (серийный) номер ФН
* @property string|null fiscalStoragePaymentAddress Адрес расчёта, указанный в ФН * @property-read string|null fiscalStoragePaymentAddress Адрес расчёта, указанный в ФН
* @property string|null groupCode Код группы кассы * @property-read string|null groupCode Код группы кассы
* @property DateTime|string|null timestamp Время и дата формирования данных, UTC * @property-read DateTime|string|null timestamp Время и дата формирования данных, UTC
* @property bool|null isShiftOpened Признак открыта смена (true) или закрыта (false) * @property-read bool|null isShiftOpened Признак открыта смена (true) или закрыта (false)
* @property int|null shiftNumber Номер смены (или "Номер закрытой смены", когда смена закрыта) * @property-read int|null shiftNumber Номер смены (или "Номер закрытой смены", когда смена закрыта)
* @property int|null shiftReceipt Номер документа за смену (или "Кол-во чеков закрытой смены", когда смена закрыта) * @property-read int|null shiftReceipt Номер документа за смену (или "Кол-во чеков закрытой смены", когда смена
* @property int|null unsentDocs Количество неотправленных документов. Указывается, если значение отлично от 0. * закрыта)
* @property DateTime|string|null firstUnsetDocTimestamp Дата первого неотправленного документа. Указывается, если * @property-read int|null unsentDocs Количество неотправленных документов. Указывается, если значение отлично от 0.
* @property-read DateTime|string|null firstUnsetDocTimestamp Дата первого неотправленного документа. Указывается, если
* есть неотправленные документы. * есть неотправленные документы.
* @property int|null networkErrorCode Код ошибки сети * @property-read int|null networkErrorCode Код ошибки сети
*/ */
final class Kkt extends Entity final class Kkt extends Entity
{ {

View File

@@ -14,8 +14,7 @@ namespace AtolOnline\Entities;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
use AtolOnline\Exceptions\{ use AtolOnline\Exceptions\{
InvalidPhoneException, InvalidPhoneException,
TooLongPayingAgentOperationException TooLongPayingAgentOperationException};
};
use AtolOnline\Traits\HasPhones; use AtolOnline\Traits\HasPhones;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -64,7 +63,7 @@ final class PayingAgent extends Entity
throw new TooLongPayingAgentOperationException($operation); throw new TooLongPayingAgentOperationException($operation);
} }
} }
$this->operation = empty($operation) ? null : $operation; $this->operation = $operation ?: null;
return $this; return $this;
} }

View File

@@ -11,20 +11,26 @@ declare(strict_types = 1);
namespace AtolOnline\Entities; namespace AtolOnline\Entities;
use AtolOnline\Constants\Constraints; use AtolOnline\{
use AtolOnline\Enums\PaymentTypes; Constants\Constraints,
use AtolOnline\Exceptions\InvalidEnumValueException; Enums\PaymentTypes,
use AtolOnline\Exceptions\NegativePaymentSumException; };
use AtolOnline\Exceptions\TooHighPaymentSumException; use AtolOnline\Exceptions\{
use JetBrains\PhpStorm\ArrayShape; InvalidEnumValueException,
use JetBrains\PhpStorm\Pure; NegativePaymentSumException,
TooHighPaymentSumException,
};
use JetBrains\PhpStorm\{
ArrayShape,
Pure
};
/** /**
* Класс, описывающий оплату * Класс, описывающий оплату
* *
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30
*/ */
class Payment extends Entity final class Payment extends Entity
{ {
/** /**
* @var int Тип оплаты * @var int Тип оплаты
@@ -41,9 +47,9 @@ class Payment extends Entity
* *
* @param int $type Тип оплаты * @param int $type Тип оплаты
* @param float $sum Сумма оплаты * @param float $sum Сумма оплаты
* @throws InvalidEnumValueException
* @throws NegativePaymentSumException * @throws NegativePaymentSumException
* @throws TooHighPaymentSumException * @throws TooHighPaymentSumException
* @throws InvalidEnumValueException
*/ */
public function __construct(int $type, float $sum) public function __construct(int $type, float $sum)
{ {

480
src/Entities/Receipt.php Normal file
View File

@@ -0,0 +1,480 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\Api\AtolResponse;
use AtolOnline\Api\Fiscalizer;
use AtolOnline\Collections\Items;
use AtolOnline\Collections\Payments;
use AtolOnline\Collections\Vats;
use AtolOnline\Constants\Constraints;
use AtolOnline\Exceptions\AuthFailedException;
use AtolOnline\Exceptions\EmptyItemsException;
use AtolOnline\Exceptions\EmptyLoginException;
use AtolOnline\Exceptions\EmptyPasswordException;
use AtolOnline\Exceptions\InvalidEntityInCollectionException;
use AtolOnline\Exceptions\InvalidInnLengthException;
use AtolOnline\Exceptions\InvalidPaymentAddressException;
use AtolOnline\Exceptions\TooLongAddCheckPropException;
use AtolOnline\Exceptions\TooLongCashierException;
use AtolOnline\Exceptions\TooLongPaymentAddressException;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
/**
* Класс, описывающий документ прихода, расхода, возврата прихода, возврата расхода
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 17
*/
final class Receipt extends Entity
{
/**
* Тип документа
*/
public const DOC_TYPE = 'receipt';
/**
* @var Client Покупатель
*/
protected Client $client;
/**
* @todo вынести в трейт?
* @var Company Продавец
*/
protected Company $company;
/**
* @var AgentInfo|null Агент
*/
protected ?AgentInfo $agent_info = null;
/**
* @var Supplier|null Поставщик
*/
protected ?Supplier $supplier = null;
/**
* @var Items Коллекция предметов расчёта
*/
protected Items $items;
/**
* @todo вынести в трейт?
* @var Payments Коллекция оплат
*/
protected Payments $payments;
/**
* @var Vats|null Коллекция ставок НДС
*/
protected ?Vats $vats = null;
/**
* @var float Итоговая сумма чека
*/
protected float $total = 0;
/**
* @todo вынести в трейт?
* @var string|null ФИО кассира
*/
protected ?string $cashier = null;
/**
* @var string|null Дополнительный реквизит
*/
protected ?string $add_check_props = null;
/**
* @var AdditionalUserProps|null Дополнительный реквизит пользователя
*/
protected ?AdditionalUserProps $add_user_props = null;
/**
* Конструктор
*
* @param Client $client
* @param Company $company
* @param Items $items
* @param Payments $payments
* @throws EmptyItemsException
* @throws InvalidEntityInCollectionException
*/
public function __construct(Client $client, Company $company, Items $items, Payments $payments)
{
$this->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;
}
}

View File

@@ -16,8 +16,7 @@ use AtolOnline\Exceptions\InvalidEnumValueException;
use AtolOnline\Helpers; use AtolOnline\Helpers;
use JetBrains\PhpStorm\{ use JetBrains\PhpStorm\{
ArrayShape, ArrayShape,
Pure Pure};
};
/** /**
* Класс, описывающий ставку НДС * Класс, описывающий ставку НДС
@@ -92,12 +91,12 @@ final class Vat extends Entity
*/ */
public function setSum(float $rubles): self public function setSum(float $rubles): self
{ {
$this->sum = $rubles; $this->sum = round($rubles, 2);
return $this; return $this;
} }
/** /**
* Возвращает sdрасчитанный итоговый размер ставки НДС в рублях * Возвращает расчитанный итоговый размер ставки НДС в рублях
* *
* @return float * @return float
* @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/ * @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/
@@ -107,7 +106,8 @@ final class Vat extends Entity
#[Pure] #[Pure]
public function getCalculated(): float public function getCalculated(): float
{ {
return Helpers::toRub(match ($this->getType()) { return Helpers::toRub(
match ($this->getType()) {
VatTypes::VAT10 => Helpers::toKop($this->sum) * 10 / 100, VatTypes::VAT10 => Helpers::toKop($this->sum) * 10 / 100,
VatTypes::VAT18 => Helpers::toKop($this->sum) * 18 / 100, VatTypes::VAT18 => Helpers::toKop($this->sum) * 18 / 100,
VatTypes::VAT20 => Helpers::toKop($this->sum) * 20 / 100, VatTypes::VAT20 => Helpers::toKop($this->sum) * 20 / 100,
@@ -115,7 +115,8 @@ final class Vat extends Entity
VatTypes::VAT118 => Helpers::toKop($this->sum) * 18 / 118, VatTypes::VAT118 => Helpers::toKop($this->sum) * 18 / 118,
VatTypes::VAT120 => Helpers::toKop($this->sum) * 20 / 120, VatTypes::VAT120 => Helpers::toKop($this->sum) * 20 / 120,
default => 0, default => 0,
}); }
);
} }
/** /**

View File

@@ -1,468 +0,0 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\Constants\Constraints;
use AtolOnline\Exceptions\AtolException;
use AtolOnline\Exceptions\InvalidEmailException;
use AtolOnline\Exceptions\InvalidInnLengthException;
use AtolOnline\Exceptions\InvalidJsonException;
use AtolOnline\Exceptions\TooHighPriceException;
use AtolOnline\Exceptions\TooLongCashierException;
use AtolOnline\Exceptions\TooLongClientContactException;
use AtolOnline\Exceptions\TooLongEmailException;
use AtolOnline\Exceptions\TooLongItemNameException;
use AtolOnline\Exceptions\TooLongMeasurementUnitException;
use AtolOnline\Exceptions\TooLongPaymentAddressException;
use AtolOnline\Exceptions\TooLongUserdataException;
use AtolOnline\Exceptions\TooManyException;
use AtolOnline\Exceptions\TooManyItemsException;
use AtolOnline\Exceptions\TooManyPaymentsException;
use AtolOnline\Exceptions\TooManyVatsException;
use Exception;
/**
* Класс, описывающий документ
*
* @package AtolOnline\Entities
*/
class todoDocument extends Entity
{
/**
* @var ItemArray Массив предметов расчёта
*/
protected ItemArray $items;
/**
* @var VatArray Массив ставок НДС
*/
protected VatArray $vats;
/**
* @var PaymentArray Массив оплат
*/
protected PaymentArray $payments;
/**
* @var Company Объект компании (продавца)
*/
protected Company $company;
/**
* @var Client Объект клиента (покупателя)
*/
protected Client $client;
/**
* @var float Итоговая сумма чека. Тег ФФД - 1020.
*/
protected float $total = 0;
/**
* @var string ФИО кассира. Тег ФФД - 1021.
*/
protected string $cashier;
/**
* @var todoCorrectionInfo Данные коррекции
*/
protected todoCorrectionInfo $correction_info;
/**
* todoDocument constructor.
*/
public function __construct()
{
$this->vats = new VatArray();
$this->payments = new PaymentArray();
$this->items = new ItemArray();
}
/**
* Удаляет все налоги из документа и предметов расчёта
*
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function clearVats(): todoDocument
{
$this->setVats([]);
return $this;
}
/**
* Добавляет новую ставку НДС в массив ставок НДС
*
* @param Vat $vat Объект ставки НДС
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function addVat(Vat $vat): todoDocument
{
$this->vats->add($vat);
return $this;
}
/**
* Возвращает массив ставок НДС
*
* @return Vat[]
*/
public function getVats(): array
{
return $this->vats->get();
}
/**
* Устанавливает массив ставок НДС
*
* @param Vat[] $vats Массив ставок НДС
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
* @throws Exception
*/
public function setVats(array $vats): todoDocument
{
$this->vats->set($vats);
return $this;
}
/**
* Добавляет новую оплату в массив оплат
*
* @param todoPayment $payment Объект оплаты
* @return $this
* @throws Exception
* @throws TooManyPaymentsException Слишком много оплат
*/
public function addPayment(todoPayment $payment): todoDocument
{
if (count($this->getPayments()) == 0 && !$payment->getSum()) {
$payment->setSum($this->calcTotal());
}
$this->payments->add($payment);
return $this;
}
/**
* Возвращает массив оплат
*
* @return todoPayment[]
*/
public function getPayments(): array
{
return $this->payments->get();
}
/**
* Устанавливает массив оплат
*
* @param todoPayment[] $payments Массив оплат
* @return $this
* @throws TooManyPaymentsException Слишком много оплат
*/
public function setPayments(array $payments): todoDocument
{
$this->payments->set($payments);
return $this;
}
/**
* Добавляет новый предмет расчёта в массив предметов расчёта
*
* @param Item $item Объект предмета расчёта
* @return $this
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function addItem(Item $item): todoDocument
{
$this->items->add($item);
return $this;
}
/**
* Возвращает массив предметов расчёта
*
* @return Item[]
*/
public function getItems(): array
{
return $this->items->get();
}
/**
* Устанавливает массив предметов расчёта
*
* @param Item[] $items Массив предметов расчёта
* @return $this
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function setItems(array $items): todoDocument
{
$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): todoDocument
{
$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): todoDocument
{
$this->company = $company;
return $this;
}
/**
* Возвращает ФИО кассира. Тег ФФД - 1021.
*
* @return string|null
*/
public function getCashier(): ?string
{
return $this->cashier;
}
/**
* Устанавливает ФИО кассира. Тег ФФД - 1021.
*
* @param string|null $cashier
* @return $this
* @throws TooLongCashierException
*/
public function setCashier(?string $cashier): todoDocument
{
if ($cashier !== null) {
$cashier = trim($cashier);
if (mb_strlen($cashier) > Constraints::MAX_LENGTH_CASHIER_NAME) {
throw new TooLongCashierException($cashier, Constraints::MAX_LENGTH_CASHIER_NAME);
}
}
$this->cashier = $cashier;
return $this;
}
/**
* Возвращает данные коррекции
*
* @return todoCorrectionInfo|null
*/
public function getCorrectionInfo(): ?todoCorrectionInfo
{
return $this->correction_info;
}
/**
* Устанавливает данные коррекции
*
* @param todoCorrectionInfo|null $correction_info
* @return $this
*/
public function setCorrectionInfo(?todoCorrectionInfo $correction_info): todoDocument
{
$this->correction_info = $correction_info;
return $this;
}
/**
* Пересчитывает, сохраняет и возвращает итоговую сумму чека по всем позициям (включая НДС). Тег ФФД - 1020.
*
* @return float
* @throws Exception
*/
public function calcTotal(): float
{
$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 todoDocument
* @throws TooLongEmailException
* @throws InvalidEmailException
* @throws AtolException
* @throws InvalidInnLengthException
* @throws InvalidJsonException
* @throws TooLongItemNameException
* @throws TooLongPaymentAddressException
* @throws TooLongClientContactException
* @throws TooHighPriceException
* @throws TooManyException
* @throws TooManyItemsException
* @throws TooManyPaymentsException
* @throws TooLongMeasurementUnitException
* @throws TooLongUserdataException
* @throws Exception
*/
public static function fromRaw(string $json): todoDocument
{
$array = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new InvalidJsonException();
}
$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 todoCorrectionInfo(
$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 todoPayment();
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(): array
{
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;
}
}

View File

@@ -1,36 +0,0 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Enums;
/**
* Константы, определяющие типы документов
*/
final class DocumentTypes extends Enum
{
/**
* Чек прихода, возврата прихода, расхода, возврата расхода
*/
const RECEIPT = 'receipt';
/**
* Чек коррекции
*/
const CORRECTION = 'correction';
/**
* @inheritDoc
*/
public static function getFfdTags(): array
{
return [];
}
}

View File

@@ -77,6 +77,7 @@ final class PaymentObjects extends Enum
/** /**
* Составной предмет расчёта * Составной предмет расчёта
*
* @deprecated Более не используется согласно ФФД 1.05 * @deprecated Более не используется согласно ФФД 1.05
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 25 (payment_object) * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 25 (payment_object)
*/ */

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use Exception; use Exception;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при работе с АТОЛ Онлайн * Исключение, возникающее при работе с АТОЛ Онлайн
@@ -29,11 +30,12 @@ class AtolException extends Exception
* @param string $message Сообщение * @param string $message Сообщение
* @param int[] $ffd_tags Переопредление тегов ФФД * @param int[] $ffd_tags Переопредление тегов ФФД
*/ */
#[Pure]
public function __construct(string $message = '', array $ffd_tags = []) public function __construct(string $message = '', array $ffd_tags = [])
{ {
$tags = implode(', ', $ffd_tags ?: $this->ffd_tags); $tags = implode(', ', $ffd_tags ?: $this->ffd_tags);
parent::__construct( parent::__construct(
($message ?: $this->message) . ($tags ? ' [Теги ФФД: ' . $tags : '') . ']' ($message ?: $this->message) . ($tags ? ' [Теги ФФД: ' . $tags . ']' : '')
); );
} }
} }

View File

@@ -11,8 +11,9 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Api\KktResponse; use AtolOnline\Api\AtolResponse;
use Exception; use Exception;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при неудачной авторизации * Исключение, возникающее при неудачной авторизации
@@ -22,10 +23,11 @@ class AuthFailedException extends Exception
/** /**
* Конструктор * Конструктор
* *
* @param KktResponse $response * @param AtolResponse $response
* @param string $message * @param string $message
*/ */
public function __construct(KktResponse $response, string $message = '') #[Pure]
public function __construct(AtolResponse $response, string $message = '')
{ {
parent::__construct(($message ?: 'Ошибка авторизации: ') . ': ' . $response); parent::__construct(($message ?: 'Ошибка авторизации: ') . ': ' . $response);
} }

View File

@@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags;
/**
* Исключение, возникающее при пустом наименовании дополнительного реквизита пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
class EmptyAddUserPropNameException extends AtolException
{
protected $message = 'Наименование дополнительного реквизита пользователя не может быть пустым';
protected array $ffd_tags = [Ffd105Tags::DOC_ADD_USER_PROP_NAME];
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags;
/**
* Исключение, возникающее при пустом наименовании дополнительного реквизита пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
class EmptyAddUserPropValueException extends AtolException
{
protected $message = 'Значение дополнительного реквизита пользователя не может быть пустым';
protected array $ffd_tags = [Ffd105Tags::DOC_ADD_USER_PROP_VALUE];
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать пустую группу ККТ
*/
class EmptyGroupException extends AtolException
{
protected $message = 'Группа ККТ должна быть указана';
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать документу пустую коллекцию предметов расчёта
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 21
*/
class EmptyItemsException extends AtolException
{
protected $message = 'Документ не может содержать пустую коллекцию предметов расчёта';
}

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать пустой логин * Исключение, возникающее при попытке указать пустой логин
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 12 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 12
*/ */
class EmptyLoginException extends AtolException class EmptyLoginException extends AtolException

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
/** /**
* Исключение, возникающее при попытке указать пустой пароль * Исключение, возникающее при попытке указать пустой пароль
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 12 * @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 12
*/ */
class EmptyPasswordException extends AtolException class EmptyPasswordException extends AtolException

View File

@@ -0,0 +1,22 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать документу пустую коллекцию оплат
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 30
*/
class EmptyPaymentsException extends AtolException
{
protected $message = 'Документ не может содержать пустую коллекцию оплат';
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при попытке указать документу пустую коллекцию ставок НДС
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 31
*/
class EmptyVatsException extends AtolException
{
protected $message = 'Документ не может содержать пустую коллекцию ставок НДС';
}

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать некорректную дату коррекции * Исключение, возникающее при попытке указать некорректную дату коррекции
@@ -26,6 +27,7 @@ class InvalidCorrectionDateException extends AtolException
* @param string $date * @param string $date
* @param string $message * @param string $message
*/ */
#[Pure]
public function __construct(string $date = '', string $message = '') public function __construct(string $date = '', string $message = '')
{ {
parent::__construct("Ошибка даты документа коррекции '$date': " . $message); parent::__construct("Ошибка даты документа коррекции '$date': " . $message);

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при ошибке валидации кода таможенной декларации * Исключение, возникающее при ошибке валидации кода таможенной декларации
@@ -25,6 +26,7 @@ class InvalidDeclarationNumberException extends AtolException
* *
* @param string $code * @param string $code
*/ */
#[Pure]
public function __construct(string $code = '') public function __construct(string $code = '')
{ {
parent::__construct("Невалидный код таможенной декларации: '$code'"); parent::__construct("Невалидный код таможенной декларации: '$code'");

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при ошибке валидации email * Исключение, возникающее при ошибке валидации email
@@ -30,6 +31,7 @@ class InvalidEmailException extends AtolException
* *
* @param string $email * @param string $email
*/ */
#[Pure]
public function __construct(string $email = '') public function __construct(string $email = '')
{ {
parent::__construct("Невалидный email: '$email'"); parent::__construct("Невалидный email: '$email'");

View File

@@ -0,0 +1,37 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
/**
* Исключение, возникающее при наличии некорректных объектов в коллекции
*/
class InvalidEntityInCollectionException extends AtolException
{
/**
* Конструктор
*
* @param string $collection_class
* @param string $expected_class
* @param mixed $actual
*/
public function __construct(string $collection_class, string $expected_class, mixed $actual)
{
if (is_object($actual)) {
$actual = $actual::class;
} elseif (is_scalar($actual)) {
$actual = '(' . gettype($actual) . ')' . var_export($actual, true);
}
parent::__construct(
"Коллекция $collection_class должна содержать объекты $expected_class, найден $actual"
);
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать ИНН некорректной длины * Исключение, возникающее при попытке указать ИНН некорректной длины
@@ -31,6 +32,7 @@ class InvalidInnLengthException extends AtolException
* @param string $inn * @param string $inn
* @param string $message * @param string $message
*/ */
#[Pure]
public function __construct(string $inn = '', string $message = '') public function __construct(string $inn = '', string $message = '')
{ {
parent::__construct( parent::__construct(

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при работе с невалидным JSON * Исключение, возникающее при работе с невалидным JSON
*/ */
@@ -19,6 +21,7 @@ class InvalidJsonException extends AtolException
/** /**
* Конструктор * Конструктор
*/ */
#[Pure]
public function __construct() public function __construct()
{ {
parent::__construct('[' . json_last_error() . '] ' . json_last_error_msg()); parent::__construct('[' . json_last_error() . '] ' . json_last_error_msg());

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при ошибке валидации кода страны происхождения товара * Исключение, возникающее при ошибке валидации кода страны происхождения товара
@@ -27,6 +28,7 @@ class InvalidOKSMCodeException extends AtolException
* *
* @param string $code * @param string $code
*/ */
#[Pure]
public function __construct(string $code) public function __construct(string $code)
{ {
parent::__construct('Невалидный код страны происхождения товара: ' . $code); parent::__construct('Невалидный код страны происхождения товара: ' . $code);

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать некорректный адрес места расчётов * Исключение, возникающее при попытке указать некорректный адрес места расчётов
@@ -28,7 +29,8 @@ class InvalidPaymentAddressException extends AtolException
* @param string $address * @param string $address
* @param string $message * @param string $message
*/ */
public function __construct($address = '', $message = "") #[Pure]
public function __construct(string $address = '', string $message = '')
{ {
parent::__construct($message ?: "Некорректный адрес места расчётов: '$address'"); parent::__construct($message ?: "Некорректный адрес места расчётов: '$address'");
} }

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при ошибке валидации номера телефона * Исключение, возникающее при ошибке валидации номера телефона
@@ -31,6 +32,7 @@ class InvalidPhoneException extends AtolException
* *
* @param string $phone * @param string $phone
*/ */
#[Pure]
public function __construct(string $phone = '') public function __construct(string $phone = '')
{ {
parent::__construct("Невалидный номер телефона: '$phone'"); parent::__construct("Невалидный номер телефона: '$phone'");

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при ошибке валидации UUID * Исключение, возникающее при ошибке валидации UUID
*/ */
@@ -19,13 +21,11 @@ class InvalidUuidException extends AtolException
/** /**
* Конструктор * Конструктор
* *
* @param $uuid * @param string $uuid
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/ */
public function __construct(?string $uuid = null) #[Pure]
public function __construct(string $uuid = '')
{ {
parent::__construct('Невалидный UUID' . ($uuid ? ': ' . $uuid : '')); parent::__construct('Невалидный UUID: ' . $uuid);
} }
} }

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать предмету расчёта отрицательный акциз * Исключение, возникающее при попытке указать предмету расчёта отрицательный акциз
@@ -26,6 +27,7 @@ class NegativeItemExciseException extends AtolException
* @param string $name * @param string $name
* @param float $excise * @param float $excise
*/ */
#[Pure]
public function __construct(string $name, float $excise) public function __construct(string $name, float $excise)
{ {
parent::__construct("Предмет расчёта '$name' не может иметь отрицательный акциз $excise"); parent::__construct("Предмет расчёта '$name' не может иметь отрицательный акциз $excise");

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать предмету расчёта отрицательную цену * Исключение, возникающее при попытке указать предмету расчёта отрицательную цену
@@ -26,6 +27,7 @@ class NegativeItemPriceException extends AtolException
* @param string $name * @param string $name
* @param float $price * @param float $price
*/ */
#[Pure]
public function __construct(string $name, float $price) public function __construct(string $name, float $price)
{ {
parent::__construct("Предмет расчёта '$name' не может иметь отрицательную цену $price"); parent::__construct("Предмет расчёта '$name' не может иметь отрицательную цену $price");

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать предмету расчёта отрицательное количество * Исключение, возникающее при попытке указать предмету расчёта отрицательное количество
@@ -26,6 +27,7 @@ class NegativeItemQuantityException extends AtolException
* @param string $name * @param string $name
* @param float $quantity * @param float $quantity
*/ */
#[Pure]
public function __construct(string $name, float $quantity) public function __construct(string $name, float $quantity)
{ {
parent::__construct("Предмет расчёта '$name' не может иметь отрицательное количество $quantity"); parent::__construct("Предмет расчёта '$name' не может иметь отрицательное количество $quantity");

View File

@@ -12,6 +12,7 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать оплате отрицательную сумму * Исключение, возникающее при попытке указать оплате отрицательную сумму
@@ -31,6 +32,7 @@ class NegativePaymentSumException extends AtolException
* *
* @param float $sum * @param float $sum
*/ */
#[Pure]
public function __construct(float $sum) public function __construct(float $sum)
{ {
parent::__construct('Размер оплаты не может быть отрицательным: ' . $sum); parent::__construct('Размер оплаты не может быть отрицательным: ' . $sum);

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке создать объект ККТ с неполными данными от монитора * Исключение, возникающее при попытке создать объект ККТ с неполными данными от монитора
*/ */
@@ -27,6 +29,7 @@ class NotEnoughMonitorDataException extends AtolException
* @param array $props_diff * @param array $props_diff
* @param string $message * @param string $message
*/ */
#[Pure]
public function __construct(array $props_diff, string $message = '') public function __construct(array $props_diff, string $message = '')
{ {
parent::__construct($message ?: $this->message . implode(', ', $props_diff)); parent::__construct($message ?: $this->message . implode(', ', $props_diff));

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать слишком высокую цену (сумму) предмета расчёта * Исключение, возникающее при попытке указать слишком высокую цену (сумму) предмета расчёта
@@ -28,6 +29,7 @@ class TooHighItemPriceException extends TooManyException
* @param string $name * @param string $name
* @param float $price * @param float $price
*/ */
#[Pure]
public function __construct(string $name, float $price) public function __construct(string $name, float $price)
{ {
parent::__construct($price, "Слишком высокая цена для предмета расчёта '$name'"); parent::__construct($price, "Слишком высокая цена для предмета расчёта '$name'");

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке добавить слишком большое количество предмета расчёта * Исключение, возникающее при попытке добавить слишком большое количество предмета расчёта
@@ -28,6 +29,7 @@ class TooHighItemQuantityException extends TooManyException
* @param string $name * @param string $name
* @param float $quantity * @param float $quantity
*/ */
#[Pure]
public function __construct(string $name, float $quantity) public function __construct(string $name, float $quantity)
{ {
parent::__construct($quantity, "Слишком большое количество предмета расчёта '$name'"); parent::__construct($quantity, "Слишком большое количество предмета расчёта '$name'");

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке получеиня слишком высокой стоимости предмета расчёта * Исключение, возникающее при попытке получеиня слишком высокой стоимости предмета расчёта
@@ -28,6 +29,7 @@ class TooHighItemSumException extends TooManyException
* @param string $name * @param string $name
* @param float $sum * @param float $sum
*/ */
#[Pure]
public function __construct(string $name, float $sum) public function __construct(string $name, float $sum)
{ {
parent::__construct($sum, "Слишком высокая стоимость предмета расчёта '$name'"); parent::__construct($sum, "Слишком высокая стоимость предмета расчёта '$name'");

View File

@@ -0,0 +1,28 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\{
Constraints,
Ffd105Tags};
/**
* Исключение, возникающее при попытке указать слишком длинное наименование дополнительного реквизита чека
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
class TooLongAddCheckPropException extends TooLongException
{
protected $message = 'Слишком длинное наименование дополнительного реквизита чека';
protected float $max = Constraints::MAX_LENGTH_ADD_CHECK_PROP;
protected array $ffd_tags = [Ffd105Tags::DOC_ADD_CHECK_PROP_VALUE];
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\{
Constraints,
Ffd105Tags};
/**
* Исключение, возникающее при попытке указать слишком длинное наименование дополнительного реквизита пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
class TooLongAddUserPropNameException extends TooLongException
{
protected $message = 'Слишком длинное наименование дополнительного реквизита пользователя';
protected float $max = Constraints::MAX_LENGTH_ADD_USER_PROP_NAME;
protected array $ffd_tags = [Ffd105Tags::DOC_ADD_USER_PROP_NAME];
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
declare(strict_types = 1);
namespace AtolOnline\Exceptions;
use AtolOnline\Constants\{
Constraints,
Ffd105Tags};
/**
* Исключение, возникающее при попытке указать слишком длинное значение дополнительного реквизита пользователя
*
* @see https://online.atol.ru/files/API_atol_online_v4.pdf Документация, стр 32
*/
class TooLongAddUserPropValueException extends TooLongException
{
protected $message = 'Слишком длинное значение дополнительного реквизита пользователя';
protected float $max = Constraints::MAX_LENGTH_ADD_USER_PROP_VALUE;
protected array $ffd_tags = [Ffd105Tags::DOC_ADD_USER_PROP_VALUE];
}

View File

@@ -18,6 +18,6 @@ use AtolOnline\Constants\Constraints;
*/ */
class TooLongCallbackUrlException extends TooLongException class TooLongCallbackUrlException extends TooLongException
{ {
protected $message = 'Слишком длинный адрес колбека'; protected $message = 'Слишком длинный callback_url';
protected float $max = Constraints::MAX_LENGTH_CALLBACK_URL; protected float $max = Constraints::MAX_LENGTH_CALLBACK_URL;
} }

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать слишком длинное что-либо * Исключение, возникающее при попытке указать слишком длинное что-либо
*/ */
@@ -33,6 +35,7 @@ class TooLongException extends AtolException
* @param string $message * @param string $message
* @param float $max * @param float $max
*/ */
#[Pure]
public function __construct(string $value, string $message = '', float $max = 0) public function __construct(string $value, string $message = '', float $max = 0)
{ {
parent::__construct( parent::__construct(

View File

@@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
use AtolOnline\Constants\Ffd105Tags; use AtolOnline\Constants\Ffd105Tags;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать слишком длинный код товара * Исключение, возникающее при попытке указать слишком длинный код товара
@@ -28,6 +29,7 @@ class TooLongItemCodeException extends TooLongException
* @param string $name * @param string $name
* @param string $code * @param string $code
*/ */
#[Pure]
public function __construct(string $name, string $code) public function __construct(string $name, string $code)
{ {
parent::__construct($code, "Слишком длинный код товара '$name'"); parent::__construct($code, "Слишком длинный код товара '$name'");

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline\Exceptions; namespace AtolOnline\Exceptions;
use JetBrains\PhpStorm\Pure;
/** /**
* Исключение, возникающее при попытке указать слишком большое количество чего-либо * Исключение, возникающее при попытке указать слишком большое количество чего-либо
*/ */
@@ -33,6 +35,7 @@ class TooManyException extends AtolException
* @param string $message * @param string $message
* @param float|null $max * @param float|null $max
*/ */
#[Pure]
public function __construct(float $value, string $message = '', ?float $max = null) public function __construct(float $value, string $message = '', ?float $max = null)
{ {
parent::__construct( parent::__construct(

View File

@@ -14,7 +14,7 @@ namespace AtolOnline\Exceptions;
use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Constraints;
/** /**
* Исключение, возникающее при попытке добавить слишком много ставок НДС в массив * Исключение, возникающее при попытке добавить слишком много ставок НДС в документ
*/ */
class TooManyVatsException extends TooManyException class TooManyVatsException extends TooManyException
{ {

View File

@@ -52,67 +52,4 @@ final class Helpers
} }
return $result; return $result;
} }
/**
* Проверяет идентичность двух классов
*
* @param object|string $class1
* @param object|string $class2
* @return bool
*/
public static function isSameClass(object|string $class1, object|string $class2): bool
{
return (is_object($class1) ? $class1::class : $class1) === (is_object($class2) ? $class2::class : $class2);
}
/**
* Тестирует наследование класса (объекта) от указанных классов
*
* @param object|string $class Объект или имя класса для проверки
* @param string[] $parents Имена классов-родителей
* @see https://www.php.net/manual/ru/function.class-parents.php
*/
public static function checkExtendsClasses(object|string $class, array $parents): bool
{
return self::checkClassesIntersection($parents, $class, 'class_parents');
}
/**
* Тестирует имплементацию классом (объектом) указанных интерфейсов
*
* @param object|string $actual Объект или имя класса для проверки
* @param string[] $interfaces Имена классов-интерфейсов
* @see https://www.php.net/manual/ru/function.class-implements.php
*/
public static function checkImplementsInterfaces(object|string $actual, array $interfaces): bool
{
return self::checkClassesIntersection($interfaces, $actual, 'class_implements');
}
/**
* Тестирует использование классом (объектом) указанных трейтов
*
* @param object|string $class
* @param string[] $traits
* @return bool
* @see https://www.php.net/manual/ru/function.class-uses.php
*/
public static function checkUsesTraits(array $traits, object|string $class): bool
{
return self::checkClassesIntersection($traits, $class, 'class_uses');
}
/**
* Проверяет пересечение классов указанной функцией SPL
*
* @param object|string $class Класс для проверки на вхождение, или объект, класс коего нужно проверить
* @param string[] $classes Массив классов, вхождение в который нужно проверить
* @param string $function class_parents|class_implements|class_uses
*/
protected static function checkClassesIntersection(array $classes, object|string $class, string $function): bool
{
$actual_classes = is_object($class) ? $function($class) : [$class::class];
return is_array($actual_classes)
&& !empty(array_intersect($classes, $actual_classes));
}
} }

View File

@@ -11,6 +11,8 @@ declare(strict_types = 1);
namespace AtolOnline; namespace AtolOnline;
use JetBrains\PhpStorm\ArrayShape;
/** /**
* Константы, определяющие параметры тестовых сред * Константы, определяющие параметры тестовых сред
* *
@@ -23,6 +25,16 @@ final class TestEnvParams
* *
* @return string[] * @return string[]
*/ */
#[ArrayShape([
'endpoint' => "string",
'company_name' => "string",
'inn' => "string",
'payment_address' => "string",
'group' => "string",
'login' => "string",
'password' => "string",
'endpoint_ofd' => "string",
])]
public static function FFD105(): array public static function FFD105(): array
{ {
return [ return [
@@ -41,7 +53,18 @@ final class TestEnvParams
* Возвращает данные для работы с тестовой средой АТОЛ Онлайн ФФД 1.2 * Возвращает данные для работы с тестовой средой АТОЛ Онлайн ФФД 1.2
* *
* @return string[] * @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 public static function FFD12(): array
{ {
return [ return [

View File

@@ -41,7 +41,7 @@ trait HasEmail
throw new InvalidEmailException($email); throw new InvalidEmailException($email);
} }
} }
$this->email = empty($email) ? null : $email; $this->email = $email ?: null;
return $this; return $this;
} }

View File

@@ -37,7 +37,7 @@ trait HasInn
throw new InvalidInnLengthException($inn); throw new InvalidInnLengthException($inn);
} }
} }
$this->inn = empty($inn) ? null : $inn; $this->inn = $inn ?: null;
return $this; return $this;
} }

View File

@@ -0,0 +1,400 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Api;
use AtolOnline\{
Constants\Constraints,
Helpers,
TestEnvParams,
Tests\BasicTestCase};
use AtolOnline\Api\{
AtolClient,
Fiscalizer};
use AtolOnline\Exceptions\{
AuthFailedException,
EmptyCorrectionNumberException,
EmptyGroupException,
EmptyItemNameException,
EmptyItemsException,
EmptyLoginException,
EmptyPasswordException,
InvalidCallbackUrlException,
InvalidCorrectionDateException,
InvalidEntityInCollectionException,
InvalidEnumValueException,
InvalidInnLengthException,
InvalidPaymentAddressException,
InvalidUuidException,
NegativeItemPriceException,
NegativeItemQuantityException,
NegativePaymentSumException,
TooHighItemPriceException,
TooHighPaymentSumException,
TooLongCallbackUrlException,
TooLongItemNameException,
TooLongPaymentAddressException,
TooManyException};
use GuzzleHttp\Exception\GuzzleException;
/**
* Набор тестов для проверки работы фискализатора
*/
class FiscalizerTest extends BasicTestCase
{
/**
* @var array Массив UUID-ов результатов регистрации документов для переиспользования
* в тестах получения их статусов фискализации
*/
private static array $registered_uuids = [];
/**
* Тестирует успешное создание объекта фискализатора без аргументов конструктора
*
* @return void
* @covers \AtolOnline\Api\Fiscalizer
*/
public function testConstructorWithourArgs(): void
{
$fisc = new Fiscalizer();
$this->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);
}
}

View File

@@ -10,8 +10,8 @@
namespace AtolOnline\Tests\Api; namespace AtolOnline\Tests\Api;
use AtolOnline\Api\AtolClient; use AtolOnline\Api\AtolClient;
use AtolOnline\Api\KktMonitor; use AtolOnline\Api\AtolResponse;
use AtolOnline\Api\KktResponse; use AtolOnline\Api\Monitor;
use AtolOnline\Entities\Kkt; use AtolOnline\Entities\Kkt;
use AtolOnline\Exceptions\AuthFailedException; use AtolOnline\Exceptions\AuthFailedException;
use AtolOnline\Exceptions\EmptyLoginException; use AtolOnline\Exceptions\EmptyLoginException;
@@ -23,51 +23,51 @@ use AtolOnline\Exceptions\TooLongPasswordException;
use AtolOnline\Helpers; use AtolOnline\Helpers;
use AtolOnline\TestEnvParams; use AtolOnline\TestEnvParams;
use AtolOnline\Tests\BasicTestCase; use AtolOnline\Tests\BasicTestCase;
use Exception;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
/** /**
* Набор тестов для проверки работы API-клиента на примере монитора ККТ * Набор тестов для проверки работы API-клиента на примере монитора ККТ
*/ */
class KktMonitorTest extends BasicTestCase class MonitorTest extends BasicTestCase
{ {
/** /**
* Возвращает объект клиента для тестирования с тестовым API АТОЛ * Возвращает объект монитора для тестирования с тестовым API АТОЛ
* *
* @return KktMonitor * @return Monitor
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
* @throws TooLongLoginException * @throws TooLongLoginException
* @throws TooLongPasswordException * @throws TooLongPasswordException
*/ */
private function newTestClient(): KktMonitor private function newTestClient(): Monitor
{ {
$credentials = TestEnvParams::FFD105(); return (new Monitor(true))
return (new KktMonitor(true)) ->setLogin(TestEnvParams::FFD105()['login'])
->setLogin($credentials['login']) ->setPassword(TestEnvParams::FFD105()['password']);
->setPassword($credentials['password']);
} }
/** /**
* Тестирует успешное создание объекта монитора без аргументов конструктора * Тестирует успешное создание объекта монитора без аргументов конструктора
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
*/ */
public function testConstructorWithoutArgs(): void public function testConstructorWithoutArgs(): void
{ {
$client = new KktMonitor(); $client = new Monitor();
$this->assertIsObject($client); $this->assertIsObject($client);
$this->assertIsSameClass(KktMonitor::class, $client); $this->assertIsSameClass(Monitor::class, $client);
$this->assertExtendsClasses($client, [AtolClient::class]); $this->assertExtendsClasses([AtolClient::class], $client);
} }
/** /**
* Тестирует успешное создание объекта монитора с аргументами конструктора * Тестирует успешное создание объекта монитора с аргументами конструктора
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin * @covers \AtolOnline\Api\Monitor::setLogin
* @covers \AtolOnline\Api\KktMonitor::getLogin * @covers \AtolOnline\Api\Monitor::getLogin
* @covers \AtolOnline\Api\KktMonitor::setPassword * @covers \AtolOnline\Api\Monitor::setPassword
* @covers \AtolOnline\Api\KktMonitor::getPassword * @covers \AtolOnline\Api\Monitor::getPassword
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
* @throws TooLongLoginException * @throws TooLongLoginException
@@ -75,51 +75,52 @@ class KktMonitorTest extends BasicTestCase
*/ */
public function testConstructorWithArgs(): void public function testConstructorWithArgs(): void
{ {
$client = new KktMonitor(false, 'login', 'password', []); $client = new Monitor(false, 'login', 'password', []);
$this->assertIsObject($client); $this->assertIsObject($client);
$this->assertIsSameClass($client, KktMonitor::class); $this->assertIsSameClass($client, Monitor::class);
$this->assertExtendsClasses($client, [AtolClient::class]); $this->assertExtendsClasses([AtolClient::class], $client);
} }
//
/** /**
* Тестирует установку и возврат логина * Тестирует установку и возврат логина
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::getLogin * @covers \AtolOnline\Api\Monitor::getLogin
* @covers \AtolOnline\Api\KktMonitor::setLogin * @covers \AtolOnline\Api\Monitor::setLogin
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws TooLongLoginException * @throws TooLongLoginException
*/ */
public function testLogin(): void public function testLogin(): void
{ {
$client = new KktMonitor(login: 'login'); $client = new Monitor(false, login: 'login');
$this->assertEquals('login', $client->getLogin()); $this->assertEquals('login', $client->getLogin());
$client = new KktMonitor(); $client = new Monitor();
$this->assertNull($client->getLogin()); $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin());
$client->setLogin('login'); $client->setLogin('login');
$this->assertEquals('login', $client->getLogin()); $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin());
} }
/** /**
* Тестирует исключение при попытке передать пустой логин в конструктор * Тестирует исключение при попытке передать пустой логин в конструктор
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin * @covers \AtolOnline\Api\Monitor::setLogin
* @covers \AtolOnline\Exceptions\EmptyLoginException * @covers \AtolOnline\Exceptions\EmptyLoginException
*/ */
public function testEmptyLoginException(): void public function testEmptyLoginException(): void
{ {
$this->expectException(EmptyLoginException::class); $this->expectException(EmptyLoginException::class);
new KktMonitor(login: ''); new Monitor(login: '');
} }
/** /**
* Тестирует исключение при попытке передать слишком длинный логин в конструктор * Тестирует исключение при попытке передать слишком длинный логин в конструктор
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setLogin * @covers \AtolOnline\Api\Monitor::setLogin
* @covers \AtolOnline\Exceptions\TooLongLoginException * @covers \AtolOnline\Exceptions\TooLongLoginException
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
@@ -129,48 +130,48 @@ class KktMonitorTest extends BasicTestCase
public function testTooLongLoginException(): void public function testTooLongLoginException(): void
{ {
$this->expectException(TooLongLoginException::class); $this->expectException(TooLongLoginException::class);
new KktMonitor(login: Helpers::randomStr(101)); new Monitor(login: Helpers::randomStr(101));
} }
/** /**
* Тестирует установку и возврат пароля * Тестирует установку и возврат пароля
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::getPassword * @covers \AtolOnline\Api\Monitor::getPassword
* @covers \AtolOnline\Api\KktMonitor::setPassword * @covers \AtolOnline\Api\Monitor::setPassword
* @throws EmptyPasswordException * @throws EmptyPasswordException
* @throws TooLongPasswordException * @throws TooLongPasswordException
*/ */
public function testPassword(): void public function testPassword(): void
{ {
$client = new KktMonitor(password: 'password'); $client = new Monitor(false, password: 'password');
$this->assertEquals('password', $client->getPassword()); $this->assertEquals('password', $client->getPassword());
$client = new KktMonitor(); $client = new Monitor();
$this->assertNull($client->getPassword()); $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword());
$client->setPassword('password'); $client->setPassword('password');
$this->assertEquals('password', $client->getPassword()); $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword());
} }
/** /**
* Тестирует исключение при попытке передать пустой пароль в конструктор * Тестирует исключение при попытке передать пустой пароль в конструктор
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setPassword * @covers \AtolOnline\Api\Monitor::setPassword
* @covers \AtolOnline\Exceptions\EmptyPasswordException * @covers \AtolOnline\Exceptions\EmptyPasswordException
*/ */
public function testEmptyPasswordException(): void public function testEmptyPasswordException(): void
{ {
$this->expectException(EmptyPasswordException::class); $this->expectException(EmptyPasswordException::class);
new KktMonitor(password: ''); new Monitor(password: '');
} }
/** /**
* Тестирует исключение при попытке передать слишком длинный пароль в конструктор * Тестирует исключение при попытке передать слишком длинный пароль в конструктор
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::setPassword * @covers \AtolOnline\Api\Monitor::setPassword
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
* @throws TooLongLoginException * @throws TooLongLoginException
@@ -179,34 +180,34 @@ class KktMonitorTest extends BasicTestCase
public function testConstructorWithLongPassword(): void public function testConstructorWithLongPassword(): void
{ {
$this->expectException(TooLongPasswordException::class); $this->expectException(TooLongPasswordException::class);
new KktMonitor(password: Helpers::randomStr(101)); new Monitor(password: Helpers::randomStr(101));
} }
/** /**
* Тестирует установку тестового режима * Тестирует установку тестового режима
* *
* @covers \AtolOnline\Api\KktMonitor::__construct * @covers \AtolOnline\Api\Monitor::__construct
* @covers \AtolOnline\Api\KktMonitor::isTestMode * @covers \AtolOnline\Api\Monitor::isTestMode
* @covers \AtolOnline\Api\KktMonitor::setTestMode * @covers \AtolOnline\Api\Monitor::setTestMode
*/ */
public function testTestMode(): void public function testTestMode(): void
{ {
$client = new KktMonitor(); $client = new Monitor();
$this->assertTrue($client->isTestMode()); $this->assertTrue($client->isTestMode());
$client = new KktMonitor(true); $client = new Monitor(true);
$this->assertTrue($client->isTestMode()); $this->assertTrue($client->isTestMode());
$client = new KktMonitor(false); $client = new Monitor(false);
$this->assertFalse($client->isTestMode()); $this->assertFalse($client->isTestMode());
$client = (new KktMonitor())->setTestMode(); $client = (new Monitor())->setTestMode();
$this->assertTrue($client->isTestMode()); $this->assertTrue($client->isTestMode());
$client = (new KktMonitor())->setTestMode(true); $client = (new Monitor())->setTestMode(true);
$this->assertTrue($client->isTestMode()); $this->assertTrue($client->isTestMode());
$client = (new KktMonitor())->setTestMode(false); $client = (new Monitor())->setTestMode(false);
$this->assertFalse($client->isTestMode()); $this->assertFalse($client->isTestMode());
} }
@@ -214,10 +215,10 @@ class KktMonitorTest extends BasicTestCase
* Тестирует авторизацию * Тестирует авторизацию
* *
* @covers \AtolOnline\Api\AtolClient::getHeaders * @covers \AtolOnline\Api\AtolClient::getHeaders
* @covers \AtolOnline\Api\KktMonitor::sendRequest * @covers \AtolOnline\Api\Monitor::sendRequest
* @covers \AtolOnline\Api\KktMonitor::getAuthEndpoint * @covers \AtolOnline\Api\Monitor::getAuthEndpoint
* @covers \AtolOnline\Api\KktMonitor::doAuth * @covers \AtolOnline\Api\Monitor::doAuth
* @covers \AtolOnline\Api\KktMonitor::auth * @covers \AtolOnline\Api\Monitor::auth
* @covers \AtolOnline\Exceptions\AuthFailedException * @covers \AtolOnline\Exceptions\AuthFailedException
* @throws EmptyLoginException * @throws EmptyLoginException
* @throws EmptyPasswordException * @throws EmptyPasswordException
@@ -237,8 +238,8 @@ class KktMonitorTest extends BasicTestCase
* Тестирует возврат токена после авторизации * Тестирует возврат токена после авторизации
* *
* @depends testAuth * @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::setToken * @covers \AtolOnline\Api\Monitor::setToken
* @covers \AtolOnline\Api\KktMonitor::getToken * @covers \AtolOnline\Api\Monitor::getToken
* @covers \AtolOnline\Exceptions\AuthFailedException * @covers \AtolOnline\Exceptions\AuthFailedException
* @throws AuthFailedException * @throws AuthFailedException
* @throws EmptyLoginException * @throws EmptyLoginException
@@ -249,7 +250,7 @@ class KktMonitorTest extends BasicTestCase
*/ */
public function testGetToken(): void public function testGetToken(): void
{ {
$client = new KktMonitor(); $client = new Monitor();
$this->assertNull($client->getToken()); $this->assertNull($client->getToken());
$this->skipIfMonitoringIsOffline(); $this->skipIfMonitoringIsOffline();
@@ -262,7 +263,7 @@ class KktMonitorTest extends BasicTestCase
* Тестирует возврат объекта последнего ответа от API * Тестирует возврат объекта последнего ответа от API
* *
* @depends testAuth * @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getResponse * @covers \AtolOnline\Api\Monitor::getLastResponse
* @covers \AtolOnline\Exceptions\AuthFailedException * @covers \AtolOnline\Exceptions\AuthFailedException
* @throws AuthFailedException * @throws AuthFailedException
* @throws EmptyLoginException * @throws EmptyLoginException
@@ -276,17 +277,17 @@ class KktMonitorTest extends BasicTestCase
$this->skipIfMonitoringIsOffline(); $this->skipIfMonitoringIsOffline();
$client = $this->newTestClient(); $client = $this->newTestClient();
$client->auth(); $client->auth();
$this->assertIsSameClass(KktResponse::class, $client->getResponse()); $this->assertIsSameClass(AtolResponse::class, $client->getLastResponse());
} }
/** /**
* [Мониторинг] Тестирует получение данных о всех ККТ * [Мониторинг] Тестирует получение данных о всех ККТ
* *
* @depends testAuth * @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getMainEndpoint * @covers \AtolOnline\Api\Monitor::getMainEndpoint
* @covers \AtolOnline\Api\AtolClient::getUrlToMethod * @covers \AtolOnline\Api\AtolClient::getUrlToMethod
* @covers \AtolOnline\Api\KktMonitor::fetchAll * @covers \AtolOnline\Api\Monitor::fetchAll
* @covers \AtolOnline\Api\KktMonitor::getAll * @covers \AtolOnline\Api\Monitor::getAll
* @covers \AtolOnline\Exceptions\AuthFailedException * @covers \AtolOnline\Exceptions\AuthFailedException
* @throws AuthFailedException * @throws AuthFailedException
* @throws EmptyLoginException * @throws EmptyLoginException
@@ -301,7 +302,8 @@ class KktMonitorTest extends BasicTestCase
$client = $this->newTestClient(); $client = $this->newTestClient();
$client->auth(); $client->auth();
$kkts = $client->getAll(); $kkts = $client->getAll();
$this->assertNotEmpty($client->getResponse()->getContent()); $sss = $kkts->where('deviceNumber', 'KKT014034');
$this->assertNotEmpty($client->getLastResponse()->getContent());
$this->assertIsCollection($kkts); $this->assertIsCollection($kkts);
$this->assertTrue($kkts->count() > 0); $this->assertTrue($kkts->count() > 0);
$this->assertIsSameClass(Kkt::class, $kkts->random()); $this->assertIsSameClass(Kkt::class, $kkts->random());
@@ -311,10 +313,10 @@ class KktMonitorTest extends BasicTestCase
* [Мониторинг] Тестирует получение данных о конкретной ККТ * [Мониторинг] Тестирует получение данных о конкретной ККТ
* *
* @depends testAuth * @depends testAuth
* @covers \AtolOnline\Api\KktMonitor::getMainEndpoint * @covers \AtolOnline\Api\Monitor::getMainEndpoint
* @covers \AtolOnline\Api\AtolClient::getUrlToMethod * @covers \AtolOnline\Api\AtolClient::getUrlToMethod
* @covers \AtolOnline\Api\KktMonitor::fetchOne * @covers \AtolOnline\Api\Monitor::fetchOne
* @covers \AtolOnline\Api\KktMonitor::getOne * @covers \AtolOnline\Api\Monitor::getOne
* @covers \AtolOnline\Entities\Kkt::__construct * @covers \AtolOnline\Entities\Kkt::__construct
* @covers \AtolOnline\Entities\Kkt::__get * @covers \AtolOnline\Entities\Kkt::__get
* @covers \AtolOnline\Entities\Kkt::jsonSerialize * @covers \AtolOnline\Entities\Kkt::jsonSerialize
@@ -327,6 +329,7 @@ class KktMonitorTest extends BasicTestCase
* @throws TooLongPasswordException * @throws TooLongPasswordException
* @throws EmptyMonitorDataException * @throws EmptyMonitorDataException
* @throws NotEnoughMonitorDataException * @throws NotEnoughMonitorDataException
* @throws Exception
*/ */
public function testMonitorGetOne(): void public function testMonitorGetOne(): void
{ {
@@ -335,9 +338,9 @@ class KktMonitorTest extends BasicTestCase
$client->auth(); $client->auth();
$serial_number = $client->getAll()->first()->serialNumber; $serial_number = $client->getAll()->first()->serialNumber;
$kkt = $client->getOne($serial_number); $kkt = $client->getOne($serial_number);
$this->assertNotEmpty($client->getResponse()); $this->assertNotEmpty($client->getLastResponse());
$this->assertIsSameClass(Kkt::class, $kkt); $this->assertIsSameClass(Kkt::class, $kkt);
$this->assertAtolable($kkt); $this->assertIsAtolable($kkt);
$this->assertNotNull($kkt->serialNumber); $this->assertNotNull($kkt->serialNumber);
$this->assertEquals($serial_number, $kkt->serialNumber); $this->assertEquals($serial_number, $kkt->serialNumber);
} }

View File

@@ -11,9 +11,39 @@ declare(strict_types = 1);
namespace AtolOnline\Tests; namespace AtolOnline\Tests;
use AtolOnline\Collections\EntityCollection;
use AtolOnline\Collections\Items;
use AtolOnline\Collections\Payments;
use AtolOnline\Collections\Vats;
use AtolOnline\Entities\Client;
use AtolOnline\Entities\Company;
use AtolOnline\Entities\Correction;
use AtolOnline\Entities\CorrectionInfo;
use AtolOnline\Entities\Entity; use AtolOnline\Entities\Entity;
use AtolOnline\Entities\Item;
use AtolOnline\Entities\Payment;
use AtolOnline\Entities\Receipt;
use AtolOnline\Entities\Vat;
use AtolOnline\Enums\CorrectionTypes;
use AtolOnline\Enums\PaymentTypes;
use AtolOnline\Enums\SnoTypes;
use AtolOnline\Enums\VatTypes;
use AtolOnline\Exceptions\EmptyCorrectionNumberException;
use AtolOnline\Exceptions\EmptyItemNameException;
use AtolOnline\Exceptions\EmptyItemsException;
use AtolOnline\Exceptions\InvalidCorrectionDateException;
use AtolOnline\Exceptions\InvalidEntityInCollectionException;
use AtolOnline\Exceptions\InvalidEnumValueException;
use AtolOnline\Exceptions\NegativeItemPriceException;
use AtolOnline\Exceptions\NegativeItemQuantityException;
use AtolOnline\Exceptions\NegativePaymentSumException;
use AtolOnline\Exceptions\TooHighItemPriceException;
use AtolOnline\Exceptions\TooHighPaymentSumException;
use AtolOnline\Exceptions\TooLongItemNameException;
use AtolOnline\Exceptions\TooManyException;
use AtolOnline\Helpers; use AtolOnline\Helpers;
use GuzzleHttp\Client; use Exception;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -23,6 +53,10 @@ use PHPUnit\Framework\TestCase;
*/ */
class BasicTestCase extends TestCase class BasicTestCase extends TestCase
{ {
//------------------------------------------------------------------------------------------------------------------
// Методы для управления тестами, использующими тестовый АТОЛ API
//------------------------------------------------------------------------------------------------------------------
/** /**
* Проверяет наличие подключения к ресурсу по URL * Проверяет наличие подключения к ресурсу по URL
* *
@@ -33,7 +67,7 @@ class BasicTestCase extends TestCase
protected function ping(string $url, int $code): bool protected function ping(string $url, int $code): bool
{ {
try { try {
$result = (new Client([ $result = (new GuzzleClient([
'http_errors' => false, 'http_errors' => false,
'timeout' => 3, 'timeout' => 3,
]))->request('GET', $url); ]))->request('GET', $url);
@@ -63,17 +97,26 @@ class BasicTestCase extends TestCase
} }
} }
//------------------------------------------------------------------------------------------------------------------
// Дополнительные ассерты
//------------------------------------------------------------------------------------------------------------------
/** /**
* Тестирует является ли объект приводимым к json-строке согласно схеме АТОЛ Онлайн * Тестирует является ли объект приводимым к json-строке согласно схеме АТОЛ Онлайн
* *
* @param Entity $entity * @param Entity|EntityCollection $entity
* @param array|null $json_structure * @param array|null $json_structure
* @covers \AtolOnline\Entities\Entity::jsonSerialize
* @covers \AtolOnline\Entities\Entity::__toString * @covers \AtolOnline\Entities\Entity::__toString
* @covers \AtolOnline\Entities\Entity::toArray
* @covers \AtolOnline\Entities\Entity::jsonSerialize
* @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
* @throws Exception
*/ */
public function assertAtolable(Entity $entity, ?array $json_structure = null): void public function assertIsAtolable(Entity|EntityCollection $entity, ?array $json_structure = null): void
{ {
$this->assertIsArray($entity->jsonSerialize()); $this->assertIsArray($entity->jsonSerialize());
$this->assertIsArray($entity->toArray());
$this->assertEquals($entity->jsonSerialize(), $entity->toArray());
$this->assertIsString((string)$entity); $this->assertIsString((string)$entity);
$this->assertJson((string)$entity); $this->assertJson((string)$entity);
if (!is_null($json_structure)) { if (!is_null($json_structure)) {
@@ -81,68 +124,134 @@ class BasicTestCase extends TestCase
} }
} }
//------------------------------------------------------------------------------------------------------------------
// Ассерты проверки наследования
//------------------------------------------------------------------------------------------------------------------
/** /**
* Тестирует идентичность двух классов * Тестирует идентичность двух классов
* *
* @param object|string $expected * @param object|string $expected Ожидаемый класс
* @param object|string $actual * @param object|string $actual Фактический класс
*/ */
public function assertIsSameClass(object|string $expected, object|string $actual): void public function assertIsSameClass(object|string $expected, object|string $actual): void
{ {
$this->assertTrue(Helpers::isSameClass($expected, $actual)); $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 object|string $class * @param array $expected Массив ожидаемых имён классов-родителей
* @param string[] $parents * @param object|string $actual Объект или имя класса для проверки
*/ */
public function assertExtendsClasses(object|string $class, array $parents): void public function assertExtendsClasses(array $expected, object|string $actual): void
{ {
$this->assertTrue(Helpers::checkExtendsClasses($class, $parents)); $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 object|string $class * @param string[] $expected Массив ожидаемых имён интерфейсов
* @param string[] $interfaces * @param object|string $actual Объект или имя класса для проверки
*/ */
public function assertImplementsInterfaces(object|string $class, array $interfaces): void public function assertImplementsInterfaces(array $expected, object|string $actual): void
{ {
$this->assertTrue(Helpers::checkImplementsInterfaces($class, $interfaces)); $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 object|string $class * @param string[] $expected Массив ожидаемых имён трейтов
* @param string[] $traits * @param object|string $actual Объект или имя класса для проверки
*/ */
public function assertUsesTraits(object|string $class, array $traits): void public function assertUsesTraits(array $expected, object|string $actual): void
{ {
$this->assertTrue(Helpers::checkUsesTraits($traits, $class)); $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 $expected * @param mixed $value
*/ */
public function assertIsCollection(mixed $expected): void public function assertIsCollection(mixed $value): void
{ {
$this->assertIsObject($expected); $this->assertIsObject($value);
$this->assertIsIterable($expected); $this->assertIsIterable($value);
$this->assertIsSameClass($expected, Collection::class); $this->assertTrue(
$this->checkisSameClass(Collection::class, $value) ||
$this->checkExtendsClasses([Collection::class], $value)
);
} }
//------------------------------------------------------------------------------------------------------------------
// Провайдеры данных для прогона тестов
//------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------
/** /**
* Провайдер строк, которые приводятся к null * Провайдер строк, которые приводятся к null
* *
* @return array<array<string|null>> * @return array
*/ */
public function providerNullableStrings(): array public function providerNullableStrings(): array
{ {
@@ -222,4 +331,121 @@ class BasicTestCase extends TestCase
['abc.def@mail#archive.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)),
);
}
} }

View File

@@ -0,0 +1,69 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Collections;
use AtolOnline\{
Collections\Items,
Constants\Constraints,
Tests\BasicTestCase};
use AtolOnline\Exceptions\{
EmptyItemNameException,
EmptyItemsException,
InvalidEntityInCollectionException,
NegativeItemPriceException,
NegativeItemQuantityException,
TooHighItemPriceException,
TooLongItemNameException,
TooManyException,
TooManyItemsException};
/**
* Набор тестов для проверки работы класса коллекции предметов расчёта
*/
class ItemsTest extends BasicTestCase
{
/**
* Тестирует выброс исключения при установке слишком большого количества предметов расчёта
*
* @covers \AtolOnline\Collections\EntityCollection
* @covers \AtolOnline\Collections\EntityCollection::checkCount
* @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
* @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
* @covers \AtolOnline\Exceptions\TooManyItemsException
* @throws EmptyItemNameException
* @throws NegativeItemPriceException
* @throws NegativeItemQuantityException
* @throws TooHighItemPriceException
* @throws TooLongItemNameException
* @throws TooManyException
* @throws InvalidEntityInCollectionException
*/
public function testTooManyItemsException()
{
$this->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();
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Collections;
use AtolOnline\{
Collections\Payments,
Constants\Constraints,
Exceptions\EmptyPaymentsException,
Exceptions\InvalidEntityInCollectionException,
Exceptions\InvalidEnumValueException,
Exceptions\NegativePaymentSumException,
Exceptions\TooHighPaymentSumException,
Exceptions\TooManyPaymentsException,
Tests\BasicTestCase};
/**
* Набор тестов для проверки работы класса коллекции оплат
*/
class PaymentsTest extends BasicTestCase
{
/**
* Тестирует выброс исключения при установке слишком большого количества оплат
*
* @covers \AtolOnline\Collections\EntityCollection
* @covers \AtolOnline\Collections\EntityCollection::checkCount
* @covers \AtolOnline\Exceptions\TooManyPaymentsException
* @throws InvalidEnumValueException
* @throws NegativePaymentSumException
* @throws TooHighPaymentSumException
* @throws InvalidEntityInCollectionException
*/
public function testTooManyPaymentsExceptionByConstructor()
{
$this->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();
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Collections;
use AtolOnline\{
Collections\Vats,
Constants\Constraints,
Entities\Payment,
Enums\PaymentTypes,
Exceptions\EmptyVatsException,
Exceptions\InvalidEntityInCollectionException,
Exceptions\InvalidEnumValueException,
Exceptions\NegativePaymentSumException,
Exceptions\TooHighPaymentSumException,
Exceptions\TooManyVatsException,
Tests\BasicTestCase};
use Exception;
/**
* Набор тестов для проверки работы класса коллекции объектов на примере класса коллекции ставок НДС
*/
class VatsTest extends BasicTestCase
{
/**
* Тестирует создание коллекции ставок
*
* @covers \AtolOnline\Collections\EntityCollection
* @covers \AtolOnline\Collections\EntityCollection::checkCount
* @covers \AtolOnline\Collections\EntityCollection::checkItemsClasses
* @covers \AtolOnline\Collections\EntityCollection::jsonSerialize
* @throws InvalidEnumValueException
* @throws Exception
*/
public function testConstructor()
{
$vats = new Vats($this->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();
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Entities;
use AtolOnline\{
Constants\Constraints,
Entities\AdditionalUserProps,
Exceptions\EmptyAddUserPropNameException,
Exceptions\EmptyAddUserPropValueException,
Exceptions\TooLongAddUserPropNameException,
Exceptions\TooLongAddUserPropValueException,
Helpers,
Tests\BasicTestCase};
use Exception;
/**
* Набор тестов для проверки работы класса ставки дополнительного реквизита
*/
class AdditionalUserPropsTest extends BasicTestCase
{
/**
* Тестирует конструктор
*
* @covers \AtolOnline\Entities\AdditionalUserProps
* @covers \AtolOnline\Entities\AdditionalUserProps::getName
* @covers \AtolOnline\Entities\AdditionalUserProps::setName
* @covers \AtolOnline\Entities\AdditionalUserProps::setValue
* @covers \AtolOnline\Entities\AdditionalUserProps::getValue
* @covers \AtolOnline\Entities\AdditionalUserProps::jsonSerialize
* @throws Exception
*/
public function testConstructor(): void
{
$this->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', '');
}
}

View File

@@ -19,8 +19,8 @@ use AtolOnline\{
Exceptions\InvalidInnLengthException, Exceptions\InvalidInnLengthException,
Exceptions\InvalidPhoneException, Exceptions\InvalidPhoneException,
Exceptions\TooLongPayingAgentOperationException, Exceptions\TooLongPayingAgentOperationException,
Tests\BasicTestCase Tests\BasicTestCase};
}; use Exception;
/** /**
* Набор тестов для проверки работы класса агента * Набор тестов для проверки работы класса агента
@@ -32,10 +32,11 @@ class AgentInfoTest extends BasicTestCase
* *
* @covers \AtolOnline\Entities\AgentInfo * @covers \AtolOnline\Entities\AgentInfo
* @covers \AtolOnline\Entities\AgentInfo::jsonSerialize * @covers \AtolOnline\Entities\AgentInfo::jsonSerialize
* @throws Exception
*/ */
public function testConstructorWithoutArgs(): void public function testConstructorWithoutArgs(): void
{ {
$this->assertAtolable(new AgentInfo(), []); $this->assertIsAtolable(new AgentInfo(), []);
} }
/** /**
@@ -58,23 +59,24 @@ class AgentInfoTest extends BasicTestCase
* @throws TooLongPayingAgentOperationException * @throws TooLongPayingAgentOperationException
* @throws InvalidInnLengthException * @throws InvalidInnLengthException
* @throws InvalidEnumValueException * @throws InvalidEnumValueException
* @throws Exception
*/ */
public function testConstructorWithArgs(): void public function testConstructorWithArgs(): void
{ {
$this->assertAtolable(new AgentInfo(null), []); $this->assertIsAtolable(new AgentInfo(null), []);
$this->assertAtolable(new AgentInfo(AgentTypes::ANOTHER), ['type' => AgentTypes::ANOTHER]); $this->assertIsAtolable(new AgentInfo(AgentTypes::ANOTHER), ['type' => AgentTypes::ANOTHER]);
$this->assertAtolable(new AgentInfo(pagent: new PayingAgent()), []); $this->assertIsAtolable(new AgentInfo(pagent: new PayingAgent()), []);
$this->assertAtolable(new AgentInfo(mt_operator: new MoneyTransferOperator()), []); $this->assertIsAtolable(new AgentInfo(mt_operator: new MoneyTransferOperator()), []);
$this->assertAtolable(new AgentInfo(rp_operator: new ReceivePaymentsOperator()), []); $this->assertIsAtolable(new AgentInfo(rp_operator: new ReceivePaymentsOperator()), []);
$this->assertAtolable(new AgentInfo( $this->assertIsAtolable(new AgentInfo(
AgentTypes::ANOTHER, AgentTypes::ANOTHER,
new PayingAgent(), new PayingAgent(),
new ReceivePaymentsOperator(), new ReceivePaymentsOperator(),
new MoneyTransferOperator(), new MoneyTransferOperator(),
), ['type' => AgentTypes::ANOTHER]); ), ['type' => AgentTypes::ANOTHER]);
$this->assertAtolable(new AgentInfo( $this->assertIsAtolable(new AgentInfo(
AgentTypes::ANOTHER, AgentTypes::ANOTHER,
new PayingAgent('test', ['+79518888888']), new PayingAgent('test', ['+79518888888']),
new ReceivePaymentsOperator(['+79519999999']), new ReceivePaymentsOperator(['+79519999999']),

View File

@@ -9,15 +9,17 @@
namespace AtolOnline\Tests\Entities; namespace AtolOnline\Tests\Entities;
use AtolOnline\{ use AtolOnline\{Entities\Client,
Entities\Client,
Exceptions\InvalidEmailException, Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException, Exceptions\InvalidInnLengthException,
Exceptions\InvalidPhoneException, Exceptions\InvalidPhoneException,
Exceptions\TooLongClientNameException, Exceptions\TooLongClientNameException,
Exceptions\TooLongEmailException, Exceptions\TooLongEmailException,
Helpers, Helpers,
Tests\BasicTestCase}; Tests\BasicTestCase
};
use BadMethodCallException;
use Exception;
/** /**
* Набор тестов для проверки работы класса покупателя * Набор тестов для проверки работы класса покупателя
@@ -29,10 +31,11 @@ class ClientTest extends BasicTestCase
* *
* @covers \AtolOnline\Entities\Client * @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::jsonSerialize * @covers \AtolOnline\Entities\Client::jsonSerialize
* @throws Exception
*/ */
public function testConstructorWithoutArgs(): void public function testConstructorWithoutArgs(): void
{ {
$this->assertAtolable(new Client(), []); $this->assertIsAtolable(new Client(), []);
} }
/** /**
@@ -48,14 +51,16 @@ class ClientTest extends BasicTestCase
* @covers \AtolOnline\Entities\Client::getPhone * @covers \AtolOnline\Entities\Client::getPhone
* @covers \AtolOnline\Entities\Client::getEmail * @covers \AtolOnline\Entities\Client::getEmail
* @covers \AtolOnline\Entities\Client::getInn * @covers \AtolOnline\Entities\Client::getInn
* @throws Exception
*/ */
public function testConstructorWithArgs(): void public function testConstructorWithArgs(): void
{ {
$this->assertAtolable(new Client('John Doe'), ['name' => 'John Doe']); $this->assertIsAtolable(new Client('John Doe'), ['name' => 'John Doe']);
$this->assertAtolable(new Client(email: 'john@example.com'), ['email' => 'john@example.com']); $this->assertIsAtolable(new Client(email: 'john@example.com'), ['email' => 'john@example.com']);
$this->assertAtolable(new Client(phone: '+1/22/99*73s dsdas654 5s6'), ['phone' => '+122997365456']); $this->assertIsAtolable(new Client(phone: '+1/22/99*73s dsdas654 5s6'), ['phone' => '+122997365456']);
$this->assertAtolable(new Client(inn: '+fasd3\qe3fs_=nac99013928czc'), ['inn' => '3399013928']); $this->assertIsAtolable(new Client(inn: '+fasd3\qe3fs_=nac99013928czc'), ['inn' => '3399013928']);
$this->assertAtolable(new Client( $this->assertIsAtolable(
new Client(
'John Doe', 'John Doe',
'john@example.com', 'john@example.com',
'+1/22/99*73s dsdas654 5s6', // +122997365456 '+1/22/99*73s dsdas654 5s6', // +122997365456
@@ -65,7 +70,8 @@ class ClientTest extends BasicTestCase
'email' => 'john@example.com', 'email' => 'john@example.com',
'phone' => '+122997365456', 'phone' => '+122997365456',
'inn' => '3399013928', 'inn' => '3399013928',
]); ]
);
} }
/** /**
@@ -128,12 +134,12 @@ class ClientTest extends BasicTestCase
/** /**
* Тестирует установку валидного телефона * Тестирует установку валидного телефона
* *
* @throws InvalidPhoneException
* @todo актуализировать при доработатанной валидации * @todo актуализировать при доработатанной валидации
* @dataProvider providerValidPhones * @dataProvider providerValidPhones
* @covers \AtolOnline\Entities\Client * @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setPhone * @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Entities\Client::getPhone * @covers \AtolOnline\Entities\Client::getPhone
* @throws InvalidPhoneException
*/ */
public function testValidPhone(string $input, string $output): void public function testValidPhone(string $input, string $output): void
{ {
@@ -143,11 +149,11 @@ class ClientTest extends BasicTestCase
/** /**
* Тестирует установку невалидного телефона * Тестирует установку невалидного телефона
* *
* @throws InvalidPhoneException
* @todo актуализировать при доработатанной валидации * @todo актуализировать при доработатанной валидации
* @covers \AtolOnline\Entities\Client * @covers \AtolOnline\Entities\Client
* @covers \AtolOnline\Entities\Client::setPhone * @covers \AtolOnline\Entities\Client::setPhone
* @covers \AtolOnline\Exceptions\InvalidPhoneException * @covers \AtolOnline\Exceptions\InvalidPhoneException
* @throws InvalidPhoneException
*/ */
public function testInvalidPhoneException(): void public function testInvalidPhoneException(): void
{ {
@@ -244,4 +250,45 @@ class ClientTest extends BasicTestCase
$this->expectException(InvalidInnLengthException::class); $this->expectException(InvalidInnLengthException::class);
(new Client())->setInn('1234567890123'); (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']);
}
} }

View File

@@ -20,6 +20,7 @@ use AtolOnline\{
Exceptions\TooLongPaymentAddressException, Exceptions\TooLongPaymentAddressException,
Helpers, Helpers,
Tests\BasicTestCase}; Tests\BasicTestCase};
use Exception;
/** /**
* Набор тестов для проверки работы класса продавца * Набор тестов для проверки работы класса продавца
@@ -39,10 +40,11 @@ class CompanyTest extends BasicTestCase
* @covers \AtolOnline\Entities\Company::getSno * @covers \AtolOnline\Entities\Company::getSno
* @covers \AtolOnline\Entities\Company::getInn * @covers \AtolOnline\Entities\Company::getInn
* @covers \AtolOnline\Entities\Company::getPaymentAddress * @covers \AtolOnline\Entities\Company::getPaymentAddress
* @throws Exception
*/ */
public function testConstructor() public function testConstructor()
{ {
$this->assertAtolable(new Company( $this->assertIsAtolable(new Company(
$email = 'company@example.com', $email = 'company@example.com',
$sno = SnoTypes::OSN, $sno = SnoTypes::OSN,
$inn = '1234567890', $inn = '1234567890',
@@ -61,6 +63,12 @@ class CompanyTest extends BasicTestCase
* @covers \AtolOnline\Entities\Company * @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setEmail * @covers \AtolOnline\Entities\Company::setEmail
* @covers \AtolOnline\Exceptions\TooLongEmailException * @covers \AtolOnline\Exceptions\TooLongEmailException
* @throws InvalidEmailException
* @throws InvalidEnumValueException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/ */
public function testEmailTooLongException() public function testEmailTooLongException()
{ {
@@ -100,6 +108,12 @@ class CompanyTest extends BasicTestCase
* @covers \AtolOnline\Entities\Company * @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setInn * @covers \AtolOnline\Entities\Company::setInn
* @covers \AtolOnline\Exceptions\InvalidInnLengthException * @covers \AtolOnline\Exceptions\InvalidInnLengthException
* @throws InvalidEmailException
* @throws InvalidEnumValueException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/ */
public function testInvalidInnLengthException() public function testInvalidInnLengthException()
{ {
@@ -113,6 +127,12 @@ class CompanyTest extends BasicTestCase
* @covers \AtolOnline\Entities\Company * @covers \AtolOnline\Entities\Company
* @covers \AtolOnline\Entities\Company::setPaymentAddress * @covers \AtolOnline\Entities\Company::setPaymentAddress
* @covers \AtolOnline\Exceptions\TooLongPaymentAddressException * @covers \AtolOnline\Exceptions\TooLongPaymentAddressException
* @throws InvalidEmailException
* @throws InvalidEnumValueException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/ */
public function testTooLongPaymentAddressException() public function testTooLongPaymentAddressException()
{ {

View File

@@ -16,8 +16,8 @@ use AtolOnline\{
Exceptions\InvalidCorrectionDateException, Exceptions\InvalidCorrectionDateException,
Exceptions\InvalidEnumValueException, Exceptions\InvalidEnumValueException,
Helpers, Helpers,
Tests\BasicTestCase Tests\BasicTestCase};
}; use Exception;
/** /**
* Набор тестов для проверки работы класса данных коррекции * Набор тестов для проверки работы класса данных коррекции
@@ -39,10 +39,11 @@ class CorrectionInfoTest extends BasicTestCase
* @throws InvalidEnumValueException * @throws InvalidEnumValueException
* @throws InvalidCorrectionDateException * @throws InvalidCorrectionDateException
* @throws EmptyCorrectionNumberException * @throws EmptyCorrectionNumberException
* @throws Exception
*/ */
public function testConstructor(): void public function testConstructor(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', $number = Helpers::randomStr()), new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', $number = Helpers::randomStr()),
[ [
'type' => CorrectionTypes::SELF, 'type' => CorrectionTypes::SELF,

View File

@@ -0,0 +1,117 @@
<?php
/*
* Copyright (c) 2020-2021 Антон Аксенов (Anthony Axenov)
*
* This code is licensed under MIT.
* Этот код распространяется по лицензии MIT.
* https://github.com/anthonyaxenov/atol-online/blob/master/LICENSE
*/
namespace AtolOnline\Tests\Entities;
use AtolOnline\{
Constants\Constraints,
Helpers,
Tests\BasicTestCase};
use AtolOnline\Exceptions\{
EmptyCorrectionNumberException,
InvalidCorrectionDateException,
InvalidEntityInCollectionException,
InvalidEnumValueException,
NegativePaymentSumException,
TooHighPaymentSumException,
TooLongCashierException};
use Exception;
/**
* Набор тестов для проверки работы класса чека коррекции
*/
class CorrectionTest extends BasicTestCase
{
/**
* Тестирует конструктор и корректное приведение к json
*
* @return void
* @covers \AtolOnline\Entities\Correction
* @covers \AtolOnline\Entities\Correction::setCompany
* @covers \AtolOnline\Entities\Correction::setCorrectionInfo
* @covers \AtolOnline\Entities\Correction::setPayments
* @covers \AtolOnline\Entities\Correction::setVats
* @covers \AtolOnline\Entities\Correction::jsonSerialize
* @throws EmptyCorrectionNumberException
* @throws InvalidCorrectionDateException
* @throws InvalidEntityInCollectionException
* @throws InvalidEnumValueException
* @throws NegativePaymentSumException
* @throws TooHighPaymentSumException
* @throws Exception
*/
public function testConstructor(): void
{
$correction = $this->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));
}
}

View File

@@ -41,8 +41,8 @@ use AtolOnline\{
Exceptions\TooLongUserdataException, Exceptions\TooLongUserdataException,
Exceptions\TooManyException, Exceptions\TooManyException,
Helpers, Helpers,
Tests\BasicTestCase Tests\BasicTestCase};
}; use Exception;
/** /**
* Набор тестов для проверки работы класс продавца * Набор тестов для проверки работы класс продавца
@@ -67,10 +67,11 @@ class ItemTest extends BasicTestCase
* @throws NegativeItemPriceException * @throws NegativeItemPriceException
* @throws EmptyItemNameException * @throws EmptyItemNameException
* @throws NegativeItemQuantityException * @throws NegativeItemQuantityException
* @throws Exception
*/ */
public function testConstructor(): void public function testConstructor(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
new Item('test item', 2, 3), new Item('test item', 2, 3),
[ [
'name' => 'test item', 'name' => 'test item',
@@ -263,6 +264,7 @@ class ItemTest extends BasicTestCase
* @throws TooHighItemPriceException * @throws TooHighItemPriceException
* @throws NegativeItemQuantityException * @throws NegativeItemQuantityException
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws Exception
*/ */
public function testValidEnums(): void public function testValidEnums(): void
{ {
@@ -275,7 +277,7 @@ class ItemTest extends BasicTestCase
PaymentObjects::COMMODITY, PaymentObjects::COMMODITY,
$item->setPaymentObject(PaymentObjects::COMMODITY)->getPaymentObject() $item->setPaymentObject(PaymentObjects::COMMODITY)->getPaymentObject()
); );
$this->assertAtolable($item, [ $this->assertIsAtolable($item, [
'name' => 'test item', 'name' => 'test item',
'price' => 2, 'price' => 2,
'quantity' => 3, 'quantity' => 3,
@@ -332,11 +334,14 @@ class ItemTest extends BasicTestCase
* @covers \AtolOnline\Entities\Item::getVat * @covers \AtolOnline\Entities\Item::getVat
* @covers \AtolOnline\Entities\Item::jsonSerialize * @covers \AtolOnline\Entities\Item::jsonSerialize
* @throws EmptyItemNameException * @throws EmptyItemNameException
* @throws InvalidEnumValueException
* @throws NegativeItemPriceException * @throws NegativeItemPriceException
* @throws NegativeItemQuantityException * @throws NegativeItemQuantityException
* @throws TooHighItemPriceException * @throws TooHighItemPriceException
* @throws TooHighItemSumException
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws Exception
*/ */
public function testValidVatByString(): void public function testValidVatByString(): void
{ {
@@ -344,7 +349,7 @@ class ItemTest extends BasicTestCase
$this->assertIsSameClass(Vat::class, $item->getVat()); $this->assertIsSameClass(Vat::class, $item->getVat());
$this->assertEquals(VatTypes::VAT20, $item->getVat()->getType()); $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
$this->assertEquals($item->getSum(), $item->getVat()->getSum()); $this->assertEquals($item->getSum(), $item->getVat()->getSum());
$this->assertAtolable($item, [ $this->assertIsAtolable($item, [
'name' => 'test item', 'name' => 'test item',
'price' => 2, 'price' => 2,
'quantity' => 3, 'quantity' => 3,
@@ -363,11 +368,14 @@ class ItemTest extends BasicTestCase
* @covers \AtolOnline\Entities\Item::getVat * @covers \AtolOnline\Entities\Item::getVat
* @covers \AtolOnline\Entities\Item::jsonSerialize * @covers \AtolOnline\Entities\Item::jsonSerialize
* @throws EmptyItemNameException * @throws EmptyItemNameException
* @throws InvalidEnumValueException
* @throws NegativeItemPriceException * @throws NegativeItemPriceException
* @throws NegativeItemQuantityException * @throws NegativeItemQuantityException
* @throws TooHighItemPriceException * @throws TooHighItemPriceException
* @throws TooHighItemSumException
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws Exception
*/ */
public function testValidVatByObject(): void public function testValidVatByObject(): void
{ {
@@ -376,7 +384,7 @@ class ItemTest extends BasicTestCase
$this->assertIsSameClass(Vat::class, $item->getVat()); $this->assertIsSameClass(Vat::class, $item->getVat());
$this->assertEquals(VatTypes::VAT20, $item->getVat()->getType()); $this->assertEquals(VatTypes::VAT20, $item->getVat()->getType());
$this->assertEquals($item->getSum(), $item->getVat()->getSum()); $this->assertEquals($item->getSum(), $item->getVat()->getSum());
$this->assertAtolable($item, [ $this->assertIsAtolable($item, [
'name' => 'test item', 'name' => 'test item',
'price' => 2, 'price' => 2,
'quantity' => 3, 'quantity' => 3,
@@ -453,6 +461,7 @@ class ItemTest extends BasicTestCase
* @throws TooHighItemPriceException * @throws TooHighItemPriceException
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws Exception
*/ */
public function testSupplier(): void public function testSupplier(): void
{ {
@@ -463,7 +472,7 @@ class ItemTest extends BasicTestCase
); );
$item = (new Item('test item', 2, 3))->setSupplier($supplier); $item = (new Item('test item', 2, 3))->setSupplier($supplier);
$this->assertEquals($supplier, $item->getSupplier()); $this->assertEquals($supplier, $item->getSupplier());
$this->assertAtolable($item, [ $this->assertIsAtolable($item, [
'name' => 'test item', 'name' => 'test item',
'price' => 2, 'price' => 2,
'quantity' => 3, 'quantity' => 3,
@@ -489,10 +498,11 @@ class ItemTest extends BasicTestCase
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooLongUserdataException * @throws TooLongUserdataException
* @throws TooManyException * @throws TooManyException
* @throws Exception
*/ */
public function testValidUserdata(): void public function testValidUserdata(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
(new Item('test item', 2, 3)) (new Item('test item', 2, 3))
->setUserData($user_data = Helpers::randomStr(Constraints::MAX_LENGTH_USER_DATA)), ->setUserData($user_data = Helpers::randomStr(Constraints::MAX_LENGTH_USER_DATA)),
[ [
@@ -558,10 +568,11 @@ class ItemTest extends BasicTestCase
* @throws TooHighItemPriceException * @throws TooHighItemPriceException
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws Exception
*/ */
public function testCountryCode(): void public function testCountryCode(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
(new Item('test item', 2, 3))->setCountryCode('800'), (new Item('test item', 2, 3))->setCountryCode('800'),
[ [
'name' => 'test item', 'name' => 'test item',
@@ -605,10 +616,11 @@ class ItemTest extends BasicTestCase
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws InvalidDeclarationNumberException * @throws InvalidDeclarationNumberException
* @throws Exception
*/ */
public function testValidDeclarationNumber(): void public function testValidDeclarationNumber(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
(new Item('test item', 2, 3)) (new Item('test item', 2, 3))
->setDeclarationNumber($code = Helpers::randomStr()), ->setDeclarationNumber($code = Helpers::randomStr()),
[ [
@@ -675,10 +687,11 @@ class ItemTest extends BasicTestCase
* @throws EmptyItemNameException * @throws EmptyItemNameException
* @throws NegativeItemQuantityException * @throws NegativeItemQuantityException
* @throws NegativeItemExciseException * @throws NegativeItemExciseException
* @throws Exception
*/ */
public function testExcise(): void public function testExcise(): void
{ {
$this->assertAtolable( $this->assertIsAtolable(
(new Item('test item', 2, 3))->setExcise(1), (new Item('test item', 2, 3))->setExcise(1),
[ [
'name' => 'test item', 'name' => 'test item',
@@ -723,6 +736,7 @@ class ItemTest extends BasicTestCase
* @throws TooLongItemNameException * @throws TooLongItemNameException
* @throws TooManyException * @throws TooManyException
* @throws TooLongItemCodeException * @throws TooLongItemCodeException
* @throws Exception
*/ */
public function testValidNomenclatureCode(): void public function testValidNomenclatureCode(): void
{ {
@@ -736,7 +750,7 @@ class ItemTest extends BasicTestCase
$decoded = hex2bin(str_replace(' ', '', $item->getCodeHex())); $decoded = hex2bin(str_replace(' ', '', $item->getCodeHex()));
$this->assertEquals($decoded, $item->getCode()); $this->assertEquals($decoded, $item->getCode());
$this->assertAtolable($item, [ $this->assertIsAtolable($item, [
'name' => 'test item', 'name' => 'test item',
'price' => 2, 'price' => 2,
'quantity' => 3, 'quantity' => 3,

View File

@@ -57,7 +57,7 @@ class KktEntityTest extends BasicTestCase
{ {
$kkt = new Kkt((object)$this->sample_data); $kkt = new Kkt((object)$this->sample_data);
$this->assertIsSameClass(Kkt::class, $kkt); $this->assertIsSameClass(Kkt::class, $kkt);
$this->assertAtolable($kkt); $this->assertIsAtolable($kkt);
} }
/** /**

Some files were not shown because too many files have changed in this diff Show More