Начало работы по #5 и #6

- строгая типизация
- переработан класс `TestEnvParams`:
    - вынесен на уровень выше из под `AtolOnline\Constants`
    - вместо констант - две функции для получения актуальных параметров подключения по ФФД1.05 и ФФД1.2
- актуализированы `PaymentObjects` согласно #5
- исходники вынесены не уровень выше в `src`
- константы теперь enum через `myclabs/php-enum`
- новые константы `DocumentTypes`
- классы констант финализированы
- все исключения переименованы, а многие так или иначе отрефакторены (не полностью)
- новые исключения `InvalidSnoException`, `InvalidPaymentAddressException`
- `helpers.php` стал полноценным классом `Helpers`
- удалены трейты `HasEmail`, `HasInn`, `RublesKopeksConverter` (конвертация перенесена в `Helpers`)
- удалён хелпер `valid_strlen()`, вместо него теперь везде `mb_strlen()`
- сущности `Client` и `Company` получили свои имплементации для `email` и `inn`
- доработки в `BasicTestCase`
- полное покрытие тестами: `Client`, `Company`, `Helpers`
- поправлен `phpunit.xml`
- везде обновлены копирайты
- актуализированы и исправлены phpdoc, return types
- начато введение `strict_types=1`
- минимальный php теперь 8.0
- обновлены все зависимости
- подключен пакет коллекций laravel для будущего использования
- теперь можно `composer test` и `composer test-cov`
This commit is contained in:
2021-11-18 12:24:30 +08:00
parent cc944a1dbb
commit 77481884ad
78 changed files with 4220 additions and 2712 deletions

213
src/Entities/Client.php Normal file
View File

@@ -0,0 +1,213 @@
<?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,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\TooLongEmailException,
Exceptions\TooLongNameException,
Exceptions\TooLongPhoneException};
/**
* Класс Client, описывающий сущность покупателя
*
* @package AtolOnline\Entities
*/
class Client extends Entity
{
/**
* @var string|null Наименование. Тег ФФД - 1227.
*/
protected ?string $name = null;
/**
* @var string|null Email. Тег ФФД - 1008.
*/
protected ?string $email = null;
/**
* @var string|null Телефон покупателя. Тег ФФД - 1008.
*/
protected ?string $phone = null;
/**
* @var string|null ИНН. Тег ФФД - 1228.
*/
protected ?string $inn = null;
/**
* Конструктор объекта покупателя
*
* @param string|null $name Наименование. Тег ФФД - 1227.
* @param string|null $phone Email. Тег ФФД - 1008.
* @param string|null $email Телефон покупателя. Тег ФФД - 1008.
* @param string|null $inn ИНН. Тег ФФД - 1228.
* @throws TooLongNameException Слишком длинное имя
* @throws TooLongPhoneException Слишком длинный телефон
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
* @throws InvalidInnLengthException Некорректная длина ИНН
*/
public function __construct(
?string $name = null,
?string $email = null,
?string $phone = null,
?string $inn = null
) {
$name && $this->setName($name);
$email && $this->setEmail($email);
$phone && $this->setPhone($phone);
$inn && $this->setInn($inn);
}
/**
* Возвращает наименование покупателя
*
* Тег ФФД - 1227
*
* @return string|null
*/
public function getName(): ?string
{
return $this->name;
}
/**
* Устанавливает наименование покупателя
*
* Тег ФФД - 1227
*
* @param string|null $name
* @return $this
* @throws TooLongNameException
*/
public function setName(?string $name): Client
{
if (is_string($name)) {
$name = preg_replace('/[\n\r\t]/', '', trim($name));
if (mb_strlen($name) > Constraints::MAX_LENGTH_CLIENT_NAME) {
throw new TooLongNameException($name, Constraints::MAX_LENGTH_CLIENT_NAME);
}
}
$this->name = empty($name) ? null : $name;
return $this;
}
/**
* Возвращает установленный email
*
* @return string|null
*/
public function getEmail(): ?string
{
return $this->email;
}
/**
* Устанавливает email
*
* @param string|null $email
* @return $this
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
*/
public function setEmail(?string $email): self
{
if (is_string($email)) {
$email = preg_replace('/[\n\r\t]/', '', trim($email));
if (mb_strlen($email) > Constraints::MAX_LENGTH_EMAIL) {
throw new TooLongEmailException($email, Constraints::MAX_LENGTH_EMAIL);
} elseif (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidEmailException($email);
}
}
$this->email = empty($email) ? null : $email;
return $this;
}
/**
* Возвращает установленный телефон
*
* Тег ФФД - 1008
*
* @return string|null
*/
public function getPhone(): ?string
{
return $this->phone;
}
/**
* Устанавливает телефон
*
* Тег ФФД - 1008
*
* @param string|null $phone Номер телефона
* @return $this
* @throws TooLongPhoneException
*/
public function setPhone(?string $phone): Client
{
if (is_string($phone)) {
$phone = preg_replace('/[^\d]/', '', trim($phone));
if (mb_strlen($phone) > Constraints::MAX_LENGTH_CLIENT_PHONE) {
throw new TooLongPhoneException($phone, Constraints::MAX_LENGTH_CLIENT_PHONE);
}
}
$this->phone = empty($phone) ? null : "+$phone";
return $this;
}
/**
* Возвращает установленный ИНН
*
* @return string|null
*/
public function getInn(): ?string
{
return $this->inn;
}
/**
* Устанавливает ИНН
*
* @param string|null $inn
* @return $this
* @throws InvalidInnLengthException Некорректная длина ИНН
*/
public function setInn(?string $inn): self
{
if (is_string($inn)) {
$inn = preg_replace('/[^\d]/', '', trim($inn));
if (preg_match_all(Constraints::PATTERN_INN, $inn) === 0) {
throw new InvalidInnLengthException($inn);
}
}
$this->inn = empty($inn) ? null : $inn;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): object
{
$json = [];
$this->getName() && $json['name'] = $this->getName();
$this->getEmail() && $json['email'] = $this->getEmail();
$this->getPhone() && $json['phone'] = $this->getPhone();
$this->getInn() && $json['inn'] = $this->getInn();
return (object)$json;
}
}

227
src/Entities/Company.php Normal file
View File

@@ -0,0 +1,227 @@
<?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,
Constants\SnoTypes,
Exceptions\InvalidEmailException,
Exceptions\InvalidInnLengthException,
Exceptions\InvalidPaymentAddressException,
Exceptions\InvalidSnoException,
Exceptions\TooLongEmailException,
Exceptions\TooLongPaymentAddressException};
/**
* Класс, описывающий сущность компании-продавца
*
* @package AtolOnline\Entities
*/
class Company extends Entity
{
/**
* @var string|null Почта. Тег ФФД - 1117.
*/
protected ?string $email;
/**
* @var string|null Система налогообложения продавца. Тег ФФД - 1055.
*/
protected ?string $sno;
/**
* @var string|null ИНН. Тег ФФД - 1018.
*/
protected ?string $inn;
/**
* @var string|null Место расчётов (адрес интернет-магазина). Тег ФФД - 1187.
*/
protected ?string $payment_address;
/**
* Company constructor.
*
* @param string $sno
* @param string $inn
* @param string $payment_address
* @param string $email
* @throws InvalidEmailException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
* @throws InvalidSnoException
* @throws TooLongEmailException
* @throws TooLongPaymentAddressException
*/
public function __construct(
string $email,
string $sno,
string $inn,
string $payment_address,
) {
$this->setEmail($email);
$this->setSno($sno);
$this->setInn($inn);
$this->setPaymentAddress($payment_address);
}
/**
* Возвращает установленный email
*
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* Устанавливает email
*
* @param string $email
* @return $this
* @throws TooLongEmailException Слишком длинный email
* @throws InvalidEmailException Невалидный email
*/
public function setEmail(string $email): self
{
$email = preg_replace('/[\n\r\t]/', '', trim($email));
if (mb_strlen($email) > Constraints::MAX_LENGTH_EMAIL) {
throw new TooLongEmailException($email, Constraints::MAX_LENGTH_EMAIL);
} elseif (empty($email) || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidEmailException($email);
}
$this->email = $email;
return $this;
}
/**
* Возвращает установленный тип налогообложения
*
* Тег ФФД - 1055
*
* @return string
*/
public function getSno(): string
{
return $this->sno;
}
/**
* Устанавливает тип налогообложения
*
* Тег ФФД - 1055
*
* @param string $sno
* @return $this
* @throws InvalidSnoException
*/
public function setSno(string $sno): Company
{
$sno = trim($sno);
if (empty($sno) || !in_array($sno, SnoTypes::toArray())) {
throw new InvalidSnoException($sno);
}
$this->sno = $sno;
return $this;
}
/**
* Возвращает установленный ИНН
*
* Тег ФФД - 1018
*
* @return string
*/
public function getInn(): string
{
return $this->inn;
}
/**
* Устанавливает ИНН
*
* Тег ФФД - 1018
*
* @param string $inn
* @return $this
* @throws InvalidInnLengthException
*/
public function setInn(string $inn): self
{
$inn = preg_replace('/[^\d]/', '', trim($inn));
if (empty($inn) || preg_match_all(Constraints::PATTERN_INN, $inn) === 0) {
throw new InvalidInnLengthException($inn);
}
$this->inn = $inn;
return $this;
}
/**
* Возвращает установленный адрес места расчётов
*
* Тег ФФД - 1187
*
* @return string
*/
public function getPaymentAddress(): string
{
return $this->payment_address;
}
/**
* Устанавливает адрес места расчётов
*
* Тег ФФД - 1187
*
* @param string $payment_address
* @return $this
* @throws TooLongPaymentAddressException
* @throws InvalidPaymentAddressException
*/
public function setPaymentAddress(string $payment_address): Company
{
$payment_address = trim($payment_address);
if (empty($payment_address)) {
throw new InvalidPaymentAddressException();
} elseif (mb_strlen($payment_address) > Constraints::MAX_LENGTH_PAYMENT_ADDRESS) {
throw new TooLongPaymentAddressException($payment_address, Constraints::MAX_LENGTH_PAYMENT_ADDRESS);
}
$this->payment_address = $payment_address;
return $this;
}
/**
* @inheritDoc
* @throws InvalidEmailException
* @throws InvalidSnoException
* @throws InvalidInnLengthException
* @throws InvalidPaymentAddressException
*/
public function jsonSerialize(): object
{
return (object)[
'email' => $this->email
? $this->getEmail()
: throw new InvalidEmailException(),
'sno' => $this->sno
? $this->getSno()
: throw new InvalidSnoException(),
'inn' => $this->inn
? $this->getInn()
: throw new InvalidInnLengthException(),
'payment_address' => $this->payment_address
? $this->getPaymentAddress()
: throw new InvalidPaymentAddressException(),
];
}
}

View File

@@ -0,0 +1,171 @@
<?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;
/**
* Класс CorrectionInfo, описывающий данные чек коррекции
*/
class CorrectionInfo extends Entity
{
/**
* @var string Тип коррекции. Тег ФФД - 1173.
*/
protected string $type;
/**
* @var string Дата документа основания для коррекции. Тег ФФД - 1178.
*/
protected string $base_date;
/**
* @var string Номер документа основания для коррекции. Тег ФФД - 1179.
*/
protected string $base_number;
/**
* @var string Описание коррекции. Тег ФФД - 1177.
*/
protected string $base_name;
/**
* CorrectionInfo constructor.
*
* @param string|null $type Тип коррекции
* @param string|null $base_date Дата документа
* @param string|null $base_number Номер документа
* @param string|null $base_name Описание коррекции
*/
public function __construct(?string $type = null, ?string $base_date = null, ?string $base_number = null, ?string $base_name = null)
{
if ($type) {
$this->setType($type);
}
if ($base_date) {
$this->setDate($base_date);
}
if ($base_number) {
$this->setNumber($base_number);
}
if ($base_name) {
$this->setName($base_name);
}
}
/**
* Возвращает номер документа основания для коррекции.
* Тег ФФД - 1179.
*
* @return string|null
*/
public function getNumber(): ?string
{
return $this->base_number;
}
/**
* Устанавливает номер документа основания для коррекции.
* Тег ФФД - 1179.
*
* @param string $number
* @return $this
*/
public function setNumber(string $number): CorrectionInfo
{
$this->base_number = trim($number);
return $this;
}
/**
* Возвращает описание коррекции.
* Тег ФФД - 1177.
*
* @return string|null
*/
public function getName(): ?string
{
return $this->base_name;
}
/**
* Устанавливает описание коррекции.
* Тег ФФД - 1177.
*
* @param string $name
* @return $this
*/
public function setName(string $name): CorrectionInfo
{
$this->base_name = trim($name);
return $this;
}
/**
* Возвращает дату документа основания для коррекции.
* Тег ФФД - 1178.
*
* @return string|null
*/
public function getDate(): ?string
{
return $this->base_date;
}
/**
* Устанавливает дату документа основания для коррекции.
* Тег ФФД - 1178.
*
* @param string $date Строка в формате d.m.Y
* @return $this
*/
public function setDate(string $date): CorrectionInfo
{
$this->base_date = $date;
return $this;
}
/**
* Возвращает тип коррекции.
* Тег ФФД - 1173.
*
* @return string|null
*/
public function getType(): ?string
{
return $this->type;
}
/**
* Устанавливает тип коррекции.
* Тег ФФД - 1173.
*
* @param string $type
* @return $this
*/
public function setType(string $type): CorrectionInfo
{
$this->type = $type;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
return [
'type' => $this->getType() ?? '', // обязателен
'base_date' => $this->getDate() ?? '', // обязателен
'base_number' => $this->getNumber() ?? '', // обязателен
'base_name' => $this->getName() ?? '' // не обязателен
];
}
}

468
src/Entities/Document.php Normal file
View File

@@ -0,0 +1,468 @@
<?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\BasicTooManyException;
use AtolOnline\Exceptions\InvalidEmailException;
use AtolOnline\Exceptions\InvalidInnLengthException;
use AtolOnline\Exceptions\InvalidJsonException;
use AtolOnline\Exceptions\TooHighPriceException;
use AtolOnline\Exceptions\TooLongCashierException;
use AtolOnline\Exceptions\TooLongEmailException;
use AtolOnline\Exceptions\TooLongNameException;
use AtolOnline\Exceptions\TooLongPaymentAddressException;
use AtolOnline\Exceptions\TooLongPhoneException;
use AtolOnline\Exceptions\TooLongUnitException;
use AtolOnline\Exceptions\TooLongUserdataException;
use AtolOnline\Exceptions\TooManyItemsException;
use AtolOnline\Exceptions\TooManyPaymentsException;
use AtolOnline\Exceptions\TooManyVatsException;
use Exception;
/**
* Класс, описывающий документ
*
* @package AtolOnline\Entities
*/
class Document 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 CorrectionInfo Данные коррекции
*/
protected CorrectionInfo $correction_info;
/**
* Document constructor.
*/
public function __construct()
{
$this->vats = new VatArray();
$this->payments = new PaymentArray();
$this->items = new ItemArray();
}
/**
* Удаляет все налоги из документа и предметов расчёта
*
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function clearVats(): Document
{
$this->setVats([]);
return $this;
}
/**
* Добавляет новую ставку НДС в массив ставок НДС
*
* @param Vat $vat Объект ставки НДС
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function addVat(Vat $vat): Document
{
$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): Document
{
$this->vats->set($vats);
return $this;
}
/**
* Добавляет новую оплату в массив оплат
*
* @param Payment $payment Объект оплаты
* @return $this
* @throws Exception
* @throws TooManyPaymentsException Слишком много оплат
*/
public function addPayment(Payment $payment): Document
{
if (count($this->getPayments()) == 0 && !$payment->getSum()) {
$payment->setSum($this->calcTotal());
}
$this->payments->add($payment);
return $this;
}
/**
* Возвращает массив оплат
*
* @return Payment[]
*/
public function getPayments(): array
{
return $this->payments->get();
}
/**
* Устанавливает массив оплат
*
* @param Payment[] $payments Массив оплат
* @return $this
* @throws TooManyPaymentsException Слишком много оплат
*/
public function setPayments(array $payments): Document
{
$this->payments->set($payments);
return $this;
}
/**
* Добавляет новый предмет расчёта в массив предметов расчёта
*
* @param Item $item Объект предмета расчёта
* @return $this
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function addItem(Item $item): Document
{
$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): Document
{
$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): Document
{
$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): Document
{
$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): Document
{
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 CorrectionInfo|null
*/
public function getCorrectionInfo(): ?CorrectionInfo
{
return $this->correction_info;
}
/**
* Устанавливает данные коррекции
*
* @param CorrectionInfo|null $correction_info
* @return $this
*/
public function setCorrectionInfo(?CorrectionInfo $correction_info): Document
{
$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 Document
* @throws TooLongEmailException
* @throws InvalidEmailException
* @throws AtolException
* @throws InvalidInnLengthException
* @throws InvalidJsonException
* @throws TooLongNameException
* @throws TooLongPaymentAddressException
* @throws TooLongPhoneException
* @throws TooHighPriceException
* @throws BasicTooManyException
* @throws TooManyItemsException
* @throws TooManyPaymentsException
* @throws TooLongUnitException
* @throws TooLongUserdataException
* @throws Exception
*/
public static function fromRaw(string $json): Document
{
$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 CorrectionInfo(
$array['correction_info']['type'] ?? null,
$array['correction_info']['base_date'] ?? null,
$array['correction_info']['base_number'] ?? null,
$array['correction_info']['base_name'] ?? null,
));
}
if (isset($array['items'])) {
foreach ($array['items'] as $ar_item) {
$item = new Item(
$ar_item['name'] ?? null,
$ar_item['price'] ?? null,
$ar_item['quantity'] ?? null,
$ar_item['measurement_unit'] ?? null,
$ar_item['vat']['type'] ?? null,
$ar_item['payment_object'] ?? null,
$ar_item['payment_method'] ?? null
);
if (!empty($ar_item['user_data'])) {
$item->setUserData($ar_item['user_data'] ?? null);
}
$doc->addItem($item);
}
}
if (isset($array['payments'])) {
foreach ($array['payments'] as $ar_payment) {
$payment = new Payment();
if (isset($ar_payment['type'])) {
$payment->setType($ar_payment['type']);
}
if (isset($ar_payment['sum'])) {
$payment->setSum($ar_payment['sum']);
}
$doc->payments->add($payment);
}
}
if (isset($array['vats'])) {
foreach ($array['vats'] as $vat_payment) {
$vat = new Vat();
if (isset($vat_payment['type'])) {
$vat->setType($vat_payment['type']);
}
if (isset($vat_payment['sum'])) {
$vat->setSum($vat_payment['sum']);
}
$doc->vats->add($vat);
}
}
if (isset($array['total']) && $array['total'] != $doc->calcTotal()) {
throw new AtolException('Real total sum not equals to provided in JSON one');
}
return $doc;
}
/**
* Возвращает массив для кодирования в json
*
* @throws Exception
*/
public function jsonSerialize(): 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;
}
}

31
src/Entities/Entity.php Normal file
View File

@@ -0,0 +1,31 @@
<?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 JsonSerializable;
use Stringable;
/**
* Абстрактное описание любой сущности, представляемой как json
*/
abstract class Entity implements JsonSerializable, Stringable
{
/**
* Возвращает строковое представление json-структуры объекта
*
* @return false|string
*/
public function __toString()
{
return json_encode($this->jsonSerialize(), JSON_UNESCAPED_UNICODE);
}
}

396
src/Entities/Item.php Normal file
View File

@@ -0,0 +1,396 @@
<?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,
Exceptions\BasicTooManyException,
Exceptions\TooHighPriceException,
Exceptions\TooLongNameException,
Exceptions\TooLongUnitException,
Exceptions\TooLongUserdataException};
/**
* Предмет расчёта (товар, услуга)
*
* @package AtolOnline\Entities
*/
class Item extends Entity
{
/**
* @var string Наименование. Тег ФФД - 1030.
*/
protected string $name;
/**
* @var int Цена в копейках (с учётом скидок и наценок). Тег ФФД - 1079.
*/
protected int $price = 0;
/**
* @var float Количество, вес. Тег ФФД - 1023.
*/
protected float $quantity = 0.0;
/**
* @var float Сумма в копейках. Тег ФФД - 1043.
*/
protected float $sum = 0;
/**
* @var string Единица измерения количества. Тег ФФД - 1197.
*/
protected string $measurement_unit;
/**
* @var Vat|null Ставка НДС
*/
protected ?Vat $vat;
/**
* @var string Признак способа расчёта. Тег ФФД - 1214.
*/
protected string $payment_method;
/**
* @var string Признак объекта расчёта. Тег ФФД - 1212.
*/
protected string $payment_object;
/**
* @var string Дополнительный реквизит. Тег ФФД - 1191.
*/
protected string $user_data;
/**
* Item constructor.
*
* @param string|null $name Наименование
* @param float|null $price Цена за одну единицу
* @param float|null $quantity Количество
* @param string|null $measurement_unit Единица измерения
* @param string|null $vat_type Ставка НДС
* @param string|null $payment_object Признак
* @param string|null $payment_method Способ расчёта
* @throws TooLongNameException Слишком длинное наименование
* @throws TooHighPriceException Слишком высокая цена за одну единицу
* @throws BasicTooManyException Слишком большое количество
* @throws TooLongUnitException Слишком длинное название единицы измерения
*/
public function __construct(
?string $name = null,
?float $price = null,
?float $quantity = null,
?string $measurement_unit = null,
?string $vat_type = null,
?string $payment_object = null,
?string $payment_method = null
) {
if ($name) {
$this->setName($name);
}
if ($price) {
$this->setPrice($price);
}
if ($quantity) {
$this->setQuantity($quantity);
}
if ($measurement_unit) {
$this->setMeasurementUnit($measurement_unit);
}
if ($vat_type) {
$this->setVatType($vat_type);
}
if ($payment_object) {
$this->setPaymentObject($payment_object);
}
if ($payment_method) {
$this->setPaymentMethod($payment_method);
}
}
/**
* Возвращает наименование. Тег ФФД - 1030.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Устаналивает наименование. Тег ФФД - 1030.
*
* @param string $name Наименование
* @return $this
* @throws TooLongNameException Слишком длинное имя/наименование
*/
public function setName(string $name): self
{
$name = trim($name);
if (mb_strlen($name) > Constraints::MAX_LENGTH_ITEM_NAME) {
throw new TooLongNameException($name, Constraints::MAX_LENGTH_ITEM_NAME);
}
$this->name = $name;
return $this;
}
/**
* Возвращает цену в рублях. Тег ФФД - 1079.
*
* @return float
*/
public function getPrice(): float
{
return rubles($this->price);
}
/**
* Устанавливает цену в рублях. Тег ФФД - 1079.
*
* @param float $rubles Цена за одну единицу в рублях
* @return $this
* @throws TooHighPriceException Слишком высокая цена за одну единицу
*/
public function setPrice(float $rubles): Item
{
if ($rubles > 42949672.95) {
throw new TooHighPriceException($rubles, 42949672.95);
}
$this->price = kopeks($rubles);
$this->calcSum();
return $this;
}
/**
* Возвращает количество. Тег ФФД - 1023.
*
* @return float
*/
public function getQuantity(): float
{
return $this->quantity;
}
/**
* Устанавливает количество. Тег ФФД - 1023.
*
* @param float $quantity Количество
* @param string|null $measurement_unit Единица измерения количества
* @return $this
* @throws BasicTooManyException Слишком большое количество
* @throws TooHighPriceException Слишком высокая общая стоимость
* @throws TooLongUnitException Слишком длинное название единицы измерения
*/
public function setQuantity(float $quantity, string $measurement_unit = null): Item
{
$quantity = round($quantity, 3);
if ($quantity > 99999.999) {
throw new BasicTooManyException($quantity, 99999.999);
}
$this->quantity = $quantity;
$this->calcSum();
if ($measurement_unit) {
$this->setMeasurementUnit($measurement_unit);
}
return $this;
}
/**
* Возвращает заданную единицу измерения количества. Тег ФФД - 1197.
*
* @return string
*/
public function getMeasurementUnit(): string
{
return $this->measurement_unit;
}
/**
* Устанавливает единицу измерения количества. Тег ФФД - 1197.
*
* @param string $measurement_unit Единица измерения количества
* @return $this
* @throws TooLongUnitException Слишком длинное название единицы измерения
*/
public function setMeasurementUnit(string $measurement_unit): Item
{
$measurement_unit = trim($measurement_unit);
if (mb_strlen($measurement_unit) > Constraints::MAX_LENGTH_MEASUREMENT_UNIT) {
throw new TooLongUnitException($measurement_unit, Constraints::MAX_LENGTH_MEASUREMENT_UNIT);
}
$this->measurement_unit = $measurement_unit;
return $this;
}
/**
* Возвращает признак способа оплаты. Тег ФФД - 1214.
*
* @return string
*/
public function getPaymentMethod(): string
{
return $this->payment_method;
}
/**
* Устанавливает признак способа оплаты. Тег ФФД - 1214.
*
* @param string $payment_method Признак способа оплаты
* @return $this
* @todo Проверка допустимых значений
*/
public function setPaymentMethod(string $payment_method): Item
{
$this->payment_method = trim($payment_method);
return $this;
}
/**
* Возвращает признак предмета расчёта. Тег ФФД - 1212.
*
* @return string
*/
public function getPaymentObject(): string
{
return $this->payment_object;
}
/**
* Устанавливает признак предмета расчёта. Тег ФФД - 1212.
*
* @param string $payment_object Признак предмета расчёта
* @return $this
* @todo Проверка допустимых значений
*/
public function setPaymentObject(string $payment_object): Item
{
$this->payment_object = trim($payment_object);
return $this;
}
/**
* Возвращает ставку НДС
*
* @return Vat|null
*/
public function getVat(): ?Vat
{
return $this->vat;
}
/**
* Устанавливает ставку НДС
*
* @param string|null $vat_type Тип ставки НДС. Передать null, чтобы удалить ставку.
* @return $this
* @throws TooHighPriceException
*/
public function setVatType(?string $vat_type): Item
{
if ($vat_type) {
$this->vat
? $this->vat->setType($vat_type)
: $this->vat = new Vat($vat_type);
} else {
$this->vat = null;
}
$this->calcSum();
return $this;
}
/**
* Возвращает дополнительный реквизит. Тег ФФД - 1191.
*
* @return string|null
*/
public function getUserData(): ?string
{
return $this->user_data;
}
/**
* Устанавливает дополнительный реквизит. Тег ФФД - 1191.
*
* @param string $user_data Дополнительный реквизит. Тег ФФД - 1191.
* @return $this
* @throws TooLongUserdataException Слишком длинный дополнительный реквизит
*/
public function setUserData(string $user_data): self
{
$user_data = trim($user_data);
if (mb_strlen($user_data) > Constraints::MAX_LENGTH_USER_DATA) {
throw new TooLongUserdataException($user_data, Constraints::MAX_LENGTH_USER_DATA);
}
$this->user_data = $user_data;
return $this;
}
/**
* Возвращает стоимость. Тег ФФД - 1043.
*
* @return float
*/
public function getSum(): float
{
return rubles($this->sum);
}
/**
* Расчитывает стоимость и размер НДС на неё
*
* @return float
* @throws TooHighPriceException Слишком большая сумма
*/
public function calcSum(): float
{
$sum = $this->quantity * $this->price;
if (rubles($sum) > 42949672.95) {
throw new TooHighPriceException($sum, 42949672.95);
}
$this->sum = $sum;
if ($this->vat) {
$this->vat->setSum(rubles($sum));
}
return $this->getSum();
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$json = [
'name' => $this->getName(), // обязательно
'price' => $this->getPrice(), // обязательно
'quantity' => $this->getQuantity(), // обязательно
'sum' => $this->getSum(), // обязательно
'measurement_unit' => $this->getMeasurementUnit(),
'payment_method' => $this->getPaymentMethod(),
'payment_object' => $this->getPaymentObject()
//TODO nomenclature_code
//TODO agent_info
//TODO supplier_info
//TODO excise
//TODO country_code
//TODO declaration_number
];
if ($this->getVat()) {
$json['vat'] = $this->getVat()->jsonSerialize();
}
if ($this->getUserData()) {
$json['user_data'] = $this->getUserData();
}
return $json;
}
}

115
src/Entities/ItemArray.php Normal file
View File

@@ -0,0 +1,115 @@
<?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\Exceptions\TooManyItemsException;
/**
* Класс, описывающий массив предметов расчёта
*
* @package AtolOnline\Entities
*/
class ItemArray extends Entity
{
/**
* Максимальное количество элементов в массиве
* По документации ограничение по количеству предметов расчёта = от 1 до 100,
* однако в схеме sell не указан receipt.properties.items.maxItems
*/
public const MAX_COUNT = 100;
/**
* @var Item[] Массив предметов расчёта
*/
private array $items = [];
/**
* ItemArray constructor.
*
* @param Item[]|null $items Массив предметов расчёта
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function __construct(?array $items = null)
{
if ($items) {
$this->set($items);
}
}
/**
* Устанавливает массив предметов расчёта
*
* @param Item[] $items Массив предметов расчёта
* @return $this
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function set(array $items): ItemArray
{
if ($this->validateCount($items)) {
$this->items = $items;
}
return $this;
}
/**
* Добавляет предмет расчёта в массив
*
* @param Item $item Объект предмета расчёта
* @return $this
* @throws TooManyItemsException Слишком много предметов расчёта
*/
public function add(Item $item): ItemArray
{
if ($this->validateCount()) {
$this->items[] = $item;
}
return $this;
}
/**
* Возвращает массив предметов расчёта
*
* @return Item[]
*/
public function get(): array
{
return $this->items;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$result = [];
foreach ($this->get() as $item) {
$result[] = $item->jsonSerialize();
}
return $result;
}
/**
* Проверяет количество предметов расчёта
*
* @param Item[]|null $items Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws TooManyItemsException Слишком много предметов расчёта
*/
protected function validateCount(?array $items = null): bool
{
if ((!empty($items) && count($items) >= self::MAX_COUNT) || count($this->items) >= self::MAX_COUNT) {
throw new TooManyItemsException(count($items), self::MAX_COUNT);
}
return true;
}
}

99
src/Entities/Payment.php Normal file
View File

@@ -0,0 +1,99 @@
<?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\PaymentTypes;
/**
* Класс, описывающий оплату. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
*
* @package AtolOnline\Entities
*/
class Payment extends Entity
{
/**
* @var int Тип оплаты
*/
protected int $type;
/**
* @var float Сумма оплаты
*/
protected float $sum;
/**
* Payment constructor.
*
* @param int $payment_type Тип оплаты
* @param float $sum Сумма оплаты
*/
public function __construct(int $payment_type = PaymentTypes::ELECTRON, float $sum = 0.0)
{
$this->setType($payment_type);
$this->setSum($sum);
}
/**
* Возвращает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
*
* @return int
*/
public function getType(): int
{
return $this->type;
}
/**
* Устанавливает тип оплаты. Тег ФФД - 1031, 1081, 1215, 1216, 1217.
*
* @param int $type
* @return $this
*/
public function setType(int $type): Payment
{
$this->type = $type;
return $this;
}
/**
* Возвращает сумму оплаты
*
* @return float
*/
public function getSum(): float
{
return $this->sum;
}
/**
* Устанавливает сумму оплаты
*
* @param float $sum
* @return $this
*/
public function setSum(float $sum): Payment
{
$this->sum = $sum;
return $this;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
return [
'type' => $this->getType(),
'sum' => $this->getSum(),
];
}
}

View File

@@ -0,0 +1,113 @@
<?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\Exceptions\TooManyPaymentsException;
/**
* Класс, описывающий массив оплат
*
* @package AtolOnline\Entities
*/
class PaymentArray extends Entity
{
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 10;
/**
* @var Payment[] Массив оплат
*/
private array $payments = [];
/**
* ItemArray constructor.
*
* @param Payment[]|null $payments Массив оплат
* @throws TooManyPaymentsException Слишком много оплат
*/
public function __construct(?array $payments = null)
{
if ($payments) {
$this->set($payments);
}
}
/**
* Устанавливает массив оплат
*
* @param Payment[] $payments
* @return $this
* @throws TooManyPaymentsException Слишком много оплат
*/
public function set(array $payments): PaymentArray
{
if ($this->validateCount($payments)) {
$this->payments = $payments;
}
return $this;
}
/**
* Добавляет новую оплату к заданным
*
* @param Payment $payment Объект оплаты
* @return $this
* @throws TooManyPaymentsException Слишком много оплат
*/
public function add(Payment $payment): PaymentArray
{
if ($this->validateCount()) {
$this->payments[] = $payment;
}
return $this;
}
/**
* Возвращает массив оплат
*
* @return Payment[]
*/
public function get(): array
{
return $this->payments;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$result = [];
foreach ($this->get() as $payment) {
$result[] = $payment->jsonSerialize();
}
return $result;
}
/**
* Проверяет количество налоговых ставок
*
* @param Payment[]|null $payments Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws TooManyPaymentsException Слишком много оплат
*/
protected function validateCount(?array $payments = null): bool
{
if ((!empty($payments) && count($payments) >= self::MAX_COUNT) || count($this->payments) >= self::MAX_COUNT) {
throw new TooManyPaymentsException(count($payments), self::MAX_COUNT);
}
return true;
}
}

188
src/Entities/Vat.php Normal file
View File

@@ -0,0 +1,188 @@
<?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\VatTypes;
/**
* Класс, описывающий ставку НДС
*
* @package AtolOnline\Entities
*/
class Vat extends Entity
{
/**
* @var string Выбранный тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
*/
private string $type;
/**
* @var int Сумма в копейках, от которой пересчитывается размер НДС
*/
private int $sum_original = 0;
/**
* @var int Сумма НДС в копейках
*/
private int $sum_final = 0;
/**
* Vat constructor.
*
* @param string $type Тип ставки НДС
* @param float|null $rubles Исходная сумма в рублях, от которой нужно расчитать размер НДС
*/
public function __construct(string $type = VatTypes::NONE, float $rubles = null)
{
$this->type = $type;
if ($rubles) {
$this->setSum($rubles);
}
}
/**
* Устанавливает:
* размер НДС от суммы в копейках
*
* @param string $type Тип ставки НДС
* @param int $kopeks Копейки
* @return float|int
* @see https://nalog-nalog.ru/nds/nalogovaya_baza_nds/kak-schitat-nds-pravilno-vychislyaem-20-ot-summy-primer-algoritm/
* @see https://glavkniga.ru/situations/k500734
* @see https://www.b-kontur.ru/nds-kalkuljator-online
*/
protected static function calculator(string $type, int $kopeks)
{
switch ($type) {
case VatTypes::NONE:
case VatTypes::VAT0:
return 0;
case VatTypes::VAT10:
//return $kopeks * 10 / 100;
case VatTypes::VAT110:
return $kopeks * 10 / 110;
case VatTypes::VAT18:
//return $kopeks * 18 / 100;
case VatTypes::VAT118:
return $kopeks * 18 / 118;
case VatTypes::VAT20:
//return $kopeks * 20 / 100;
case VatTypes::VAT120:
return $kopeks * 20 / 120;
}
return 0;
}
/**
* Возвращает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
*
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* Устанавливает тип ставки НДС. Тег ФФД - 1199, 1105, 1104, 1103, 1102, 1107, 1106.
* Автоматически пересчитывает итоговый размер НДС от исходной суммы.
*
* @param string $type Тип ставки НДС
* @return $this
*/
public function setType(string $type): Vat
{
$this->type = $type;
$this->setFinal();
return $this;
}
/**
* Возвращает расчитанный итоговый размер ставки НДС в рублях. Тег ФФД - 1200.
*
* @return float
*/
public function getFinalSum(): float
{
return rubles($this->sum_final);
}
/**
* Устанавливает исходную сумму, от которой будет расчитываться итоговый размер НДС.
* Автоматически пересчитывает итоговый размер НДС от исходной суммы.
*
* @param float $rubles Сумма в рублях за предмет расчёта, из которой высчитывается размер НДС
* @return $this
*/
public function setSum(float $rubles): Vat
{
$this->sum_original = kopeks($rubles);
$this->setFinal();
return $this;
}
/**
* Возвращает исходную сумму, от которой расчитывается размер налога
*
* @return float
*/
public function getSum(): float
{
return rubles($this->sum_original);
}
/**
* Прибавляет указанную сумму к общей исходной сумме.
* Автоматически пересчитывает итоговый размер НДС от новой исходной суммы.
*
* @param float $rubles
* @return $this
*/
public function addSum(float $rubles): Vat
{
$this->sum_original += kopeks($rubles);
$this->setFinal();
return $this;
}
/**
* Расчитывает и возвращает размер НДС от указанной суммы в рублях.
* Не изменяет итоговый размер НДС.
*
* @param float|null $rubles
* @return float
*/
public function calc(float $rubles): float
{
return rubles(self::calculator($this->type, kopeks($rubles)));
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
return [
'type' => $this->getType(),
'sum' => $this->getFinalSum(),
];
}
/**
* Расчитывает и устанавливает итоговый размер ставки от исходной суммы в копейках
*/
protected function setFinal(): Vat
{
$this->sum_final = self::calculator($this->type, $this->sum_original);
return $this;
}
}

117
src/Entities/VatArray.php Normal file
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
*/
declare(strict_types = 1);
namespace AtolOnline\Entities;
use AtolOnline\Exceptions\TooManyVatsException;
/**
* Класс, описывающий массив ставок НДС
*
* @package AtolOnline\Entities
*/
class VatArray extends Entity
{
/**
* Максимальное количество элементов массива
*/
public const MAX_COUNT = 6;
/**
* @var Vat[] Массив ставок НДС
*/
private array $vats = [];
/**
* VatArray constructor.
*
* @param Vat[]|null $vats Массив ставок НДС
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function __construct(?array $vats = null)
{
if ($vats) {
$this->set($vats);
}
}
/**
* Устанавливает массив ставок НДС
*
* @param Vat[] $vats Массив ставок НДС
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function set(array $vats): VatArray
{
if ($this->validateCount($vats)) {
$this->vats = $vats;
}
return $this;
}
/**
* Добавляет новую ставку НДС в массив
*
* @param Vat $vat Объект ставки НДС
* @return $this
* @throws TooManyVatsException Слишком много ставок НДС
*/
public function add(Vat $vat): VatArray
{
if ($this->validateCount()) {
if (isset($this->vats[$vat->getType()])) {
$this->vats[$vat->getType()]->addSum($vat->getSum());
} else {
$this->vats[$vat->getType()] = $vat;
}
}
return $this;
}
/**
* Возвращает массив ставок НДС
*
* @return Vat[]
*/
public function get(): array
{
return $this->vats;
}
/**
* @inheritDoc
*/
public function jsonSerialize()
{
$result = [];
foreach ($this->get() as $vat) {
$result[] = $vat->jsonSerialize();
}
return $result;
}
/**
* Проверяет количество налоговых ставок
*
* @param Vat[]|null $vats Если передать массив, то проверит количество его элементов.
* Иначе проверит количество уже присвоенных элементов.
* @return bool true если всё хорошо, иначе выбрасывает исключение
* @throws TooManyVatsException Слишком много ставок НДС
*/
protected function validateCount(?array $vats = null): bool
{
if ((!empty($vats) && count($vats) >= self::MAX_COUNT) || count($this->vats) >= self::MAX_COUNT) {
throw new TooManyVatsException(count($vats), self::MAX_COUNT);
}
return true;
}
}