From 71d1f2900cedddfec463b8673fb8f4fe949ec460 Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Sat, 18 Dec 2021 14:45:00 +0800 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D1=8C=D1=88=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=84=D0=B8=D1=81=D0=BA=D0=B8=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - у `AtolClient` теперь возможно получить последний отправленный запрос `getLastRequest()` - у `AtolClient::auth()` удалены аргументы за ненадобностью - улучшен `Client::jsonSerialize()` - исправлен `Receipt::jsonSerialize()` - у `Receipt` и `Correction` появились методы фискализации, вкусный сахарок - удалён енам `DocumentTypes` за ненадобностью - исправлены тесты монитора и документов - рабочий фискализатор с получением результатов и покрытием --- src/Api/AtolClient.php | 69 +-- src/Api/KktFiscalizer.php | 346 +++++++-------- src/Api/KktMonitor.php | 20 +- src/Entities/Client.php | 8 +- src/Entities/Correction.php | 56 ++- src/Entities/Entity.php | 8 + src/Entities/Receipt.php | 105 ++++- src/Enums/DocumentTypes.php | 36 -- src/Exceptions/EmptyGroupException.php | 20 + .../InvalidCorrectionDateException.php | 2 + .../InvalidDeclarationNumberException.php | 2 + src/Exceptions/InvalidEmailException.php | 2 + src/Exceptions/InvalidInnLengthException.php | 2 + src/Exceptions/InvalidJsonException.php | 3 + src/Exceptions/InvalidOKSMCodeException.php | 2 + src/Exceptions/InvalidPhoneException.php | 2 + .../NegativeItemExciseException.php | 2 + src/Exceptions/NegativeItemPriceException.php | 2 + .../NegativeItemQuantityException.php | 2 + .../NegativePaymentSumException.php | 2 + .../NotEnoughMonitorDataException.php | 3 + src/Exceptions/TooHighItemPriceException.php | 2 + .../TooHighItemQuantityException.php | 2 + src/Exceptions/TooHighItemSumException.php | 2 + .../TooLongCallbackUrlException.php | 2 +- src/Exceptions/TooLongException.php | 3 + src/Exceptions/TooLongItemCodeException.php | 2 + src/Exceptions/TooManyException.php | 3 + .../Tests/Api/KktFiscalizerTest.php | 417 ++++++++++++++++++ tests/AtolOnline/Tests/Api/KktMonitorTest.php | 21 +- tests/AtolOnline/Tests/BasicTestCase.php | 65 ++- .../Tests/Entities/CorrectionTest.php | 35 +- .../AtolOnline/Tests/Entities/ReceiptTest.php | 57 +-- 33 files changed, 969 insertions(+), 336 deletions(-) delete mode 100644 src/Enums/DocumentTypes.php create mode 100644 src/Exceptions/EmptyGroupException.php create mode 100644 tests/AtolOnline/Tests/Api/KktFiscalizerTest.php diff --git a/src/Api/AtolClient.php b/src/Api/AtolClient.php index 6bae018..2c7b608 100644 --- a/src/Api/AtolClient.php +++ b/src/Api/AtolClient.php @@ -29,6 +29,16 @@ use JetBrains\PhpStorm\Pure; */ abstract class AtolClient { + /** + * @var array Последний запрос к серверу АТОЛ + */ + protected array $request; + + /** + * @var KktResponse|null Последний ответ сервера АТОЛ + */ + protected ?KktResponse $response; + /** * @var bool Флаг тестового режима */ @@ -54,11 +64,6 @@ abstract class AtolClient */ private ?string $token = null; - /** - * @var KktResponse|null Последний ответ сервера АТОЛ - */ - private ?KktResponse $response; - /** * Конструктор * @@ -88,6 +93,26 @@ abstract class AtolClient !is_null($password) && $this->setPassword($password); } + /** + * Возвращает последний запрос к серверу + * + * @return array + */ + public function getLastRequest(): array + { + return $this->request; + } + + /** + * Возвращает последний ответ сервера + * + * @return KktResponse|null + */ + public function getLastResponse(): ?KktResponse + { + return $this->response; + } + /** * Возвращает установленный флаг тестового режима * @@ -132,16 +157,6 @@ abstract class AtolClient return $this; } - /** - * Возвращает последний ответ сервера - * - * @return KktResponse|null - */ - public function getResponse(): ?KktResponse - { - return $this->response; - } - /** * Возвращает логин доступа к API * @@ -273,36 +288,29 @@ abstract class AtolClient ): KktResponse { $http_method = strtoupper(trim($http_method)); $options['headers'] = array_merge($this->getHeaders(), $options['headers'] ?? []); - if ($http_method != 'GET') { - $options['json'] = $data; - } + $http_method != 'GET' && $options['json'] = $data; + $this->request = array_merge([ + 'method' => $http_method, + 'url' => $url, + ], $options); $response = $this->http->request($http_method, $url, $options); return $this->response = new KktResponse($response); } /** * Выполняет авторизацию на сервере АТОЛ - * * Авторизация выполнится только если неизвестен токен * - * @param string|null $login - * @param string|null $password * @return bool * @throws AuthFailedException - * @throws TooLongLoginException * @throws EmptyLoginException * @throws EmptyPasswordException - * @throws TooLongPasswordException * @throws GuzzleException */ - public function auth(?string $login = null, ?string $password = null): bool + public function auth(): bool { - if (empty($this->getToken())) { - !is_null($login) && $this->setLogin($login); - !is_null($password) && $this->setPassword($password); - if ($token = $this->doAuth()) { - $this->setToken($token); - } + if (empty($this->getToken()) && $token = $this->doAuth()) { + $this->setToken($token); } return !empty($this->getToken()); } @@ -320,4 +328,5 @@ abstract class AtolClient * @return string */ abstract protected function getMainEndpoint(): string; + } diff --git a/src/Api/KktFiscalizer.php b/src/Api/KktFiscalizer.php index 47d43e3..6b593fb 100644 --- a/src/Api/KktFiscalizer.php +++ b/src/Api/KktFiscalizer.php @@ -13,30 +13,30 @@ 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; + 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; /** - * Класс для регистрации документов на ККТ + * Класс фискализатора для регистрации документов на ККТ */ class KktFiscalizer extends AtolClient { @@ -62,6 +62,7 @@ class KktFiscalizer extends AtolClient * @throws EmptyPasswordException * @throws TooLongLoginException * @throws TooLongPasswordException + * @throws EmptyGroupException * @see https://guzzle.readthedocs.io/en/latest/request-options.html */ public function __construct( @@ -75,211 +76,201 @@ class KktFiscalizer extends AtolClient !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 */ + #[Pure] public function getGroup(): ?string { - return $this->group; + return $this->isTestMode() + ? TestEnvParams::FFD105()['group'] + : $this->group; } /** - * Устанавливает URL для приёма колбеков + * Устанавливает группу доступа к ККТ * - * @param string $url + * @param string $group * @return $this - * @throws TooLongCallbackUrlException - * @throws InvalidCallbackUrlException + * @throws EmptyGroupException */ - public function setCallbackUrl(string $url): self + public function setGroup(string $group): 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; + // критерии к длине строки не описаны ни в схеме, ни в документации + empty($group = trim($group)) && throw new EmptyGroupException(); + $this->group = $group; return $this; } /** * Возвращает URL для приёма колбеков * - * @return string + * @return string|null */ - public function getCallbackUrl(): string + 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 Document $document Объект документа - * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID) - * @return KktResponse + * @param Receipt $receipt Объект документа + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null * @throws AuthFailedException - * @throws EmptyCorrectionInfoException - * @throws InvalidInnLengthException - * @throws TooLongPaymentAddressException - * @throws InvalidDocumentTypeException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function sell(Document $document, ?string $external_id = null): KktResponse + public function sell(Receipt $receipt, ?string $external_id = null): ?KktResponse { - if ($document->getCorrectionInfo()) { - throw new EmptyCorrectionInfoException('Некорректная операция над документом коррекции'); - } - return $this->registerDocument('sell', 'receipt', $document, $external_id); + return $this->registerDocument('sell', $receipt, $external_id); } /** * Регистрирует документ возврата прихода * - * @param Document $document Объект документа - * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID) - * @return KktResponse + * @param Receipt $receipt Объект документа + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null * @throws AuthFailedException - * @throws EmptyCorrectionInfoException - * @throws InvalidInnLengthException - * @throws TooLongPaymentAddressException - * @throws TooManyVatsException - * @throws InvalidDocumentTypeException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function sellRefund(Document $document, ?string $external_id = null): KktResponse + public function sellRefund(Receipt $receipt, ?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); + return $this->registerDocument('sell_refund', $receipt, $external_id); } /** * Регистрирует документ коррекции прихода * - * @param Document $document Объект документа - * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID) - * @return KktResponse + * @param Correction $correction Объект документа + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null * @throws AuthFailedException - * @throws EmptyCorrectionInfoException - * @throws InvalidInnLengthException - * @throws TooLongPaymentAddressException - * @throws TooManyItemsException - * @throws InvalidDocumentTypeException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function sellCorrection(Document $document, ?string $external_id = null): KktResponse + public function sellCorrect(Correction $correction, ?string $external_id = null): ?KktResponse { - if (!$document->getCorrectionInfo()) { - throw new EmptyCorrectionInfoException(); - } - $document->setClient(null)->setItems([]); - return $this->registerDocument('sell_correction', 'correction', $document, $external_id); + return $this->registerDocument('sell_correction', $correction, $external_id); } /** * Регистрирует документ расхода * - * @param Document $document - * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID) - * @return KktResponse + * @param Receipt $receipt Объект документа + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null * @throws AuthFailedException - * @throws EmptyCorrectionInfoException - * @throws InvalidInnLengthException - * @throws TooLongPaymentAddressException - * @throws InvalidDocumentTypeException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function buy(Document $document, ?string $external_id = null): KktResponse + public function buy(Receipt $receipt, ?string $external_id = null): ?KktResponse { - if ($document->getCorrectionInfo()) { - throw new EmptyCorrectionInfoException('Invalid operation on correction document'); - } - return $this->registerDocument('buy', 'receipt', $document, $external_id); + return $this->registerDocument('buy', $receipt, $external_id); } /** * Регистрирует документ возврата расхода * - * @param Document $document + * @param Receipt $receipt Объект документа * @param string|null $external_id Уникальный код документа (если не указан, то будет создан UUID) - * @return KktResponse + * @return KktResponse|null * @throws AuthFailedException - * @throws EmptyCorrectionInfoException - * @throws InvalidInnLengthException - * @throws TooLongPaymentAddressException - * @throws TooManyVatsException - * @throws InvalidDocumentTypeException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function buyRefund(Document $document, ?string $external_id = null): KktResponse + public function buyRefund(Receipt $receipt, ?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); + return $this->registerDocument('buy_refund', $receipt, $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 Некорректный тип документа + * @param Correction $correction Объект документа + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ - public function buyCorrection(Document $document, ?string $external_id = null): KktResponse + public function buyCorrect(Correction $correction, ?string $external_id = null): ?KktResponse { - if (!$document->getCorrectionInfo()) { - throw new EmptyCorrectionInfoException(); - } - $document->setClient(null)->setItems([]); - return $this->registerDocument('buy_correction', 'correction', $document, $external_id); + return $this->registerDocument('buy_correction', $correction, $external_id); } /** * Проверяет статус чека на ККТ один раз * * @param string $uuid UUID регистрации - * @return KktResponse + * @return KktResponse|null * @throws AuthFailedException * @throws EmptyLoginException * @throws EmptyPasswordException * @throws GuzzleException * @throws InvalidUuidException - * @throws TooLongLoginException - * @throws TooLongPasswordException */ - public function getDocumentStatus(string $uuid): KktResponse + 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); + !Uuid::isValid($uuid = trim($uuid)) && throw new InvalidUuidException($uuid); + return $this->auth() + ? $this->sendRequest('GET', $this->getFullUrl('report/' . $uuid)) + : null; } /** @@ -289,16 +280,14 @@ class KktFiscalizer extends AtolClient * @param string $uuid UUID регистрации * @param int $retry_count Количество попыток * @param int $timeout Таймаут в секундах между попытками - * @return KktResponse + * @return KktResponse|null * @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 + public function pollDocumentStatus(string $uuid, int $retry_count = 5, int $timeout = 1): ?KktResponse { $try = 0; do { @@ -317,62 +306,73 @@ class KktFiscalizer extends AtolClient * Отправляет документ на регистрацию * * @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 Слишком длинный адрес места расчётов + * @param Receipt|Correction $document Документ + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException - * @throws Exception + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException */ protected function registerDocument( string $api_method, - string $type, - Document $document, + Receipt|Correction $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); + ): ?KktResponse { + $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/v1/getToken' - : 'https://online.atol.ru/possystem/v1/getToken'; + ? 'https://testonline.atol.ru/possystem/v4/getToken' + : 'https://online.atol.ru/possystem/v4/getToken'; } /** * @inheritDoc */ + #[Pure] protected function getMainEndpoint(): string { return $this->isTestMode() ? 'https://testonline.atol.ru/possystem/v4/' : 'https://online.atol.ru/possystem/v4/'; } + + /** + * Возвращает полный URL метода API + * + * @param string $api_method + * @return string + */ + #[Pure] + protected function getFullUrl(string $api_method): string + { + return $this->getMainEndpoint() . $this->getGroup() . '/' . trim($api_method); + } } diff --git a/src/Api/KktMonitor.php b/src/Api/KktMonitor.php index a2989f0..b513f37 100644 --- a/src/Api/KktMonitor.php +++ b/src/Api/KktMonitor.php @@ -13,9 +13,11 @@ namespace AtolOnline\Api; use AtolOnline\Entities\Kkt; use AtolOnline\Exceptions\{ + AuthFailedException, + EmptyLoginException, EmptyMonitorDataException, - NotEnoughMonitorDataException -}; + EmptyPasswordException, + NotEnoughMonitorDataException}; use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Collection; use JetBrains\PhpStorm\Pure; @@ -54,16 +56,21 @@ class KktMonitor extends AtolClient * * @param int|null $limit * @param int|null $offset - * @return KktResponse + * @return KktResponse|null * @throws GuzzleException + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9 */ - protected function fetchAll(?int $limit = null, ?int $offset = null): KktResponse + protected function fetchAll(?int $limit = null, ?int $offset = null): ?KktResponse { $params = []; !is_null($limit) && $params['limit'] = $limit; !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; } /** @@ -72,6 +79,9 @@ class KktMonitor extends AtolClient * @param int|null $limit * @param int|null $offset * @return Collection + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException * @throws GuzzleException * @see https://online.atol.ru/files/API_service_information.pdf Документация, стр 9 */ diff --git a/src/Entities/Client.php b/src/Entities/Client.php index 6a1e10b..8011274 100644 --- a/src/Entities/Client.php +++ b/src/Entities/Client.php @@ -132,10 +132,10 @@ final class Client extends Entity public function jsonSerialize(): array { $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(); + !is_null($this->getName()) && $json['name'] = $this->getName(); + !is_null($this->getEmail()) && $json['email'] = $this->getEmail(); + !is_null($this->getPhone()) && $json['phone'] = $this->getPhone(); + !is_null($this->getInn()) && $json['inn'] = $this->getInn(); return $json; } } diff --git a/src/Entities/Correction.php b/src/Entities/Correction.php index 30a2736..2df653c 100644 --- a/src/Entities/Correction.php +++ b/src/Entities/Correction.php @@ -10,13 +10,22 @@ namespace AtolOnline\Entities; use AtolOnline\{ + Api\KktFiscalizer, + Api\KktResponse, Collections\Payments, Collections\Vats, Constants\Constraints}; use AtolOnline\Exceptions\{ + AuthFailedException, + EmptyLoginException, + EmptyPasswordException, InvalidEntityInCollectionException, - TooLongCashierException}; + InvalidInnLengthException, + InvalidPaymentAddressException, + TooLongCashierException, + TooLongPaymentAddressException}; use Exception; +use GuzzleHttp\Exception\GuzzleException; use JetBrains\PhpStorm\ArrayShape; /** @@ -26,6 +35,11 @@ use JetBrains\PhpStorm\ArrayShape; */ class Correction extends Entity { + /** + * Тип документа + */ + public const DOC_TYPE = 'correction'; + /** * @var Company Продавец */ @@ -194,6 +208,46 @@ class Correction extends Entity return $this; } + /** + * Регистрирует коррекцию прихода по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function sellCorrect(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->sellCorrect($this, $external_id); + } + + /** + * Регистрирует коррекцию расхода по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function buyCorrect(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->buyCorrect($this, $external_id); + } + /** * @inheritDoc * @throws InvalidEntityInCollectionException diff --git a/src/Entities/Entity.php b/src/Entities/Entity.php index 24c524c..99f3ec1 100644 --- a/src/Entities/Entity.php +++ b/src/Entities/Entity.php @@ -16,6 +16,7 @@ namespace AtolOnline\Entities; use ArrayAccess; use BadMethodCallException; use Illuminate\Contracts\Support\Arrayable; +use JetBrains\PhpStorm\ArrayShape; use JsonSerializable; use Stringable; @@ -32,6 +33,13 @@ abstract class Entity implements JsonSerializable, Stringable, Arrayable, ArrayA /** * @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(); diff --git a/src/Entities/Receipt.php b/src/Entities/Receipt.php index ee5183b..ceb3e87 100644 --- a/src/Entities/Receipt.php +++ b/src/Entities/Receipt.php @@ -11,15 +11,24 @@ declare(strict_types = 1); namespace AtolOnline\Entities; +use AtolOnline\Api\KktFiscalizer; +use AtolOnline\Api\KktResponse; 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; /** * Класс, описывающий документ прихода, расхода, возврата прихода, возврата расхода @@ -28,6 +37,11 @@ use Exception; */ final class Receipt extends Entity { + /** + * Тип документа + */ + public const DOC_TYPE = 'receipt'; + /** * @var Client Покупатель */ @@ -360,6 +374,86 @@ final class Receipt extends Entity return $this; } + /** + * Регистрирует приход по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function sell(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->sell($this, $external_id); + } + + /** + * Регистрирует возврат прихода по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function sellRefund(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->sellRefund($this, $external_id); + } + + /** + * Регистрирует расход по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function buy(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->buy($this, $external_id); + } + + /** + * Регистрирует возврат расхода по текущему документу + * + * @param KktFiscalizer $fiscalizer Объект фискализатора + * @param string|null $external_id Уникальный код документа (если не указан, то будет создан новый UUID) + * @return KktResponse|null + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws TooLongPaymentAddressException + */ + public function buyRefund(KktFiscalizer $fiscalizer, ?string $external_id = null): ?KktResponse + { + return $fiscalizer->buyRefund($this, $external_id); + } + /** * Возвращает массив для кодирования в json * @@ -368,18 +462,19 @@ final class Receipt extends Entity public function jsonSerialize(): array { $json = [ - 'client' => $this->getClient(), - 'company' => $this->getCompany(), + '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(); - $this->getSupplier()?->jsonSerialize() && $json['supplier_info'] = $this->getSupplier(); + $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(); + $this->getAddUserProps()?->jsonSerialize() && + $json['additional_user_props'] = $this->getAddUserProps()->jsonSerialize(); return $json; } } diff --git a/src/Enums/DocumentTypes.php b/src/Enums/DocumentTypes.php deleted file mode 100644 index 8cf0869..0000000 --- a/src/Enums/DocumentTypes.php +++ /dev/null @@ -1,36 +0,0 @@ -message . implode(', ', $props_diff)); diff --git a/src/Exceptions/TooHighItemPriceException.php b/src/Exceptions/TooHighItemPriceException.php index 61a813d..84c6930 100644 --- a/src/Exceptions/TooHighItemPriceException.php +++ b/src/Exceptions/TooHighItemPriceException.php @@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions; use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Ffd105Tags; +use JetBrains\PhpStorm\Pure; /** * Исключение, возникающее при попытке указать слишком высокую цену (сумму) предмета расчёта @@ -28,6 +29,7 @@ class TooHighItemPriceException extends TooManyException * @param string $name * @param float $price */ + #[Pure] public function __construct(string $name, float $price) { parent::__construct($price, "Слишком высокая цена для предмета расчёта '$name'"); diff --git a/src/Exceptions/TooHighItemQuantityException.php b/src/Exceptions/TooHighItemQuantityException.php index 785e0a8..245e9a6 100644 --- a/src/Exceptions/TooHighItemQuantityException.php +++ b/src/Exceptions/TooHighItemQuantityException.php @@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions; use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Ffd105Tags; +use JetBrains\PhpStorm\Pure; /** * Исключение, возникающее при попытке добавить слишком большое количество предмета расчёта @@ -28,6 +29,7 @@ class TooHighItemQuantityException extends TooManyException * @param string $name * @param float $quantity */ + #[Pure] public function __construct(string $name, float $quantity) { parent::__construct($quantity, "Слишком большое количество предмета расчёта '$name'"); diff --git a/src/Exceptions/TooHighItemSumException.php b/src/Exceptions/TooHighItemSumException.php index b296bee..66d7b13 100644 --- a/src/Exceptions/TooHighItemSumException.php +++ b/src/Exceptions/TooHighItemSumException.php @@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions; use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Ffd105Tags; +use JetBrains\PhpStorm\Pure; /** * Исключение, возникающее при попытке получеиня слишком высокой стоимости предмета расчёта @@ -28,6 +29,7 @@ class TooHighItemSumException extends TooManyException * @param string $name * @param float $sum */ + #[Pure] public function __construct(string $name, float $sum) { parent::__construct($sum, "Слишком высокая стоимость предмета расчёта '$name'"); diff --git a/src/Exceptions/TooLongCallbackUrlException.php b/src/Exceptions/TooLongCallbackUrlException.php index b5e9307..0d39009 100644 --- a/src/Exceptions/TooLongCallbackUrlException.php +++ b/src/Exceptions/TooLongCallbackUrlException.php @@ -18,6 +18,6 @@ use AtolOnline\Constants\Constraints; */ class TooLongCallbackUrlException extends TooLongException { - protected $message = 'Слишком длинный адрес колбека'; + protected $message = 'Слишком длинный callback_url'; protected float $max = Constraints::MAX_LENGTH_CALLBACK_URL; } diff --git a/src/Exceptions/TooLongException.php b/src/Exceptions/TooLongException.php index 952af10..dcb5632 100644 --- a/src/Exceptions/TooLongException.php +++ b/src/Exceptions/TooLongException.php @@ -11,6 +11,8 @@ declare(strict_types = 1); namespace AtolOnline\Exceptions; +use JetBrains\PhpStorm\Pure; + /** * Исключение, возникающее при попытке указать слишком длинное что-либо */ @@ -33,6 +35,7 @@ class TooLongException extends AtolException * @param string $message * @param float $max */ + #[Pure] public function __construct(string $value, string $message = '', float $max = 0) { parent::__construct( diff --git a/src/Exceptions/TooLongItemCodeException.php b/src/Exceptions/TooLongItemCodeException.php index e9dba47..79ff94a 100644 --- a/src/Exceptions/TooLongItemCodeException.php +++ b/src/Exceptions/TooLongItemCodeException.php @@ -13,6 +13,7 @@ namespace AtolOnline\Exceptions; use AtolOnline\Constants\Constraints; use AtolOnline\Constants\Ffd105Tags; +use JetBrains\PhpStorm\Pure; /** * Исключение, возникающее при попытке указать слишком длинный код товара @@ -28,6 +29,7 @@ class TooLongItemCodeException extends TooLongException * @param string $name * @param string $code */ + #[Pure] public function __construct(string $name, string $code) { parent::__construct($code, "Слишком длинный код товара '$name'"); diff --git a/src/Exceptions/TooManyException.php b/src/Exceptions/TooManyException.php index d999287..0d715d7 100644 --- a/src/Exceptions/TooManyException.php +++ b/src/Exceptions/TooManyException.php @@ -11,6 +11,8 @@ declare(strict_types = 1); namespace AtolOnline\Exceptions; +use JetBrains\PhpStorm\Pure; + /** * Исключение, возникающее при попытке указать слишком большое количество чего-либо */ @@ -33,6 +35,7 @@ class TooManyException extends AtolException * @param string $message * @param float|null $max */ + #[Pure] public function __construct(float $value, string $message = '', ?float $max = null) { parent::__construct( diff --git a/tests/AtolOnline/Tests/Api/KktFiscalizerTest.php b/tests/AtolOnline/Tests/Api/KktFiscalizerTest.php new file mode 100644 index 0000000..cee78db --- /dev/null +++ b/tests/AtolOnline/Tests/Api/KktFiscalizerTest.php @@ -0,0 +1,417 @@ +assertIsObject($fisc); + $this->assertIsSameClass(KktFiscalizer::class, $fisc); + $this->assertExtendsClasses([AtolClient::class], $fisc); + } + + /** + * Тестирует установку и возврат группы ККТ + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer + * @covers \AtolOnline\Api\KktFiscalizer::getGroup + * @covers \AtolOnline\Api\KktFiscalizer::setGroup + */ + public function testGroup(): void + { + // test mode + $this->assertEquals( + TestEnvParams::FFD105()['group'], + (new KktFiscalizer(group: 'group'))->getGroup() + ); + // prod mode + $this->assertEquals('group', (new KktFiscalizer(false, group: 'group'))->getGroup()); + $this->assertNull((new KktFiscalizer(false))->getGroup()); + } + + /** + * Тестирует выброс исключения при попытке передать пустую группу ККТ в конструктор + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer + * @covers \AtolOnline\Api\KktFiscalizer::setGroup + * @covers \AtolOnline\Exceptions\EmptyGroupException + */ + public function testEmptyGroupException(): void + { + $this->expectException(EmptyGroupException::class); + new KktFiscalizer(group: "\n\r \0\t"); + } + + /** + * Тестирует выброс исключения при попытке установить слишком длинный адрес колбека + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer::setCallbackUrl + * @covers \AtolOnline\Exceptions\TooLongCallbackUrlException + * @throws InvalidCallbackUrlException + * @throws TooLongCallbackUrlException + */ + public function testTooLongCallbackUrlException(): void + { + $this->expectException(TooLongCallbackUrlException::class); + (new KktFiscalizer())->setCallbackUrl(Helpers::randomStr(Constraints::MAX_LENGTH_CALLBACK_URL + 1)); + } + + /** + * Тестирует выброс исключения при попытке установить слишком длинный адрес колбека + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer::setCallbackUrl + * @covers \AtolOnline\Exceptions\InvalidCallbackUrlException + * @throws InvalidCallbackUrlException + * @throws TooLongCallbackUrlException + */ + public function testInvalidCallbackUrlException(): void + { + $this->expectException(InvalidCallbackUrlException::class); + (new KktFiscalizer())->setCallbackUrl(Helpers::randomStr()); + } + + /** + * Тестирует обнуление адреса колбека + * + * @param mixed $param + * @return void + * @covers \AtolOnline\Api\KktFiscalizer::setCallbackUrl + * @covers \AtolOnline\Api\KktFiscalizer::getCallbackUrl + * @dataProvider providerNullableStrings + * @throws InvalidCallbackUrlException + * @throws TooLongCallbackUrlException + */ + public function testNullableCallbackUrl(mixed $param): void + { + $this->assertNull((new KktFiscalizer())->setCallbackUrl($param)->getCallbackUrl()); + } + + /** + * Тестирует регистрацию документа прихода + * + * @return void + * @covers \AtolOnline\Entities\Receipt::sell + * @covers \AtolOnline\Api\KktFiscalizer::sell + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::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 TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws TooManyException + * @throws GuzzleException + */ + public function testSell(): void + { + $fisc_result = $this->newReceipt()->sell(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует регистрацию документа возврата прихода + * + * @return void + * @covers \AtolOnline\Entities\Receipt::sellRefund + * @covers \AtolOnline\Api\KktFiscalizer::sellRefund + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::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 TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws TooManyException + * @throws GuzzleException + */ + public function testSellRefund(): void + { + $fisc_result = $this->newReceipt()->sellRefund(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует регистрацию документа возврата прихода + * + * @return void + * @covers \AtolOnline\Entities\Correction::sellCorrect + * @covers \AtolOnline\Api\KktFiscalizer::sellCorrect + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::registerDocument + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidEnumValueException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws NegativePaymentSumException + * @throws TooHighPaymentSumException + * @throws TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws EmptyCorrectionNumberException + * @throws InvalidCorrectionDateException + */ + public function testSellCorrect(): void + { + $fisc_result = $this->newCorrection()->sellCorrect(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует регистрацию документа расхода + * + * @return void + * @covers \AtolOnline\Entities\Receipt::buy + * @covers \AtolOnline\Api\KktFiscalizer::buy + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::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 TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws TooManyException + * @throws GuzzleException + */ + public function testBuy(): void + { + $fisc_result = $this->newReceipt()->buy(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует регистрацию документа возврата расхода + * + * @return void + * @covers \AtolOnline\Entities\Receipt::buyRefund + * @covers \AtolOnline\Api\KktFiscalizer::buyRefund + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::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 TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws TooManyException + * @throws GuzzleException + */ + public function testBuyRefund(): void + { + $fisc_result = $this->newReceipt()->buyRefund(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует регистрацию документа возврата прихода + * + * @return void + * @covers \AtolOnline\Entities\Correction::buyCorrect + * @covers \AtolOnline\Api\KktFiscalizer::buyCorrect + * @covers \AtolOnline\Api\KktFiscalizer::getFullUrl + * @covers \AtolOnline\Api\KktFiscalizer::getAuthEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::getMainEndpoint + * @covers \AtolOnline\Api\KktFiscalizer::registerDocument + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidEntityInCollectionException + * @throws InvalidEnumValueException + * @throws InvalidInnLengthException + * @throws InvalidPaymentAddressException + * @throws NegativePaymentSumException + * @throws TooHighPaymentSumException + * @throws TooLongLoginException + * @throws TooLongPasswordException + * @throws TooLongPaymentAddressException + * @throws EmptyCorrectionNumberException + * @throws InvalidCorrectionDateException + */ + public function testBuyCorrect(): void + { + $fisc_result = $this->newCorrection()->buyCorrect(new KktFiscalizer()); + $this->assertTrue($fisc_result->isValid()); + $this->assertEquals('wait', $fisc_result->getContent()->status); + self::$registered_uuids[] = $fisc_result->getContent()->uuid; + } + + /** + * Тестирует разовое получение статуса фискализации документа + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer::getDocumentStatus + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidUuidException + * @throws TooLongLoginException + * @throws TooLongPasswordException + */ + public function testGetDocumentStatus(): void + { + $fisc_status = (new KktFiscalizer())->getDocumentStatus(self::$registered_uuids[0]); + $this->assertTrue($fisc_status->isValid()); + $this->assertTrue(in_array($fisc_status->getContent()->status, ['wait', 'done'])); + } + + /** + * Тестирует опрос API на получение статуса фискализации документа + * + * @return void + * @covers \AtolOnline\Api\KktFiscalizer::pollDocumentStatus + * @throws AuthFailedException + * @throws EmptyLoginException + * @throws EmptyPasswordException + * @throws GuzzleException + * @throws InvalidUuidException + * @throws TooLongLoginException + * @throws TooLongPasswordException + */ + public function testPollDocumentStatus(): void + { + $fisc_status = (new KktFiscalizer())->pollDocumentStatus(self::$registered_uuids[1]); + $this->assertTrue($fisc_status->isValid()); + $this->assertEquals('done', $fisc_status->getContent()->status); + } + +} diff --git a/tests/AtolOnline/Tests/Api/KktMonitorTest.php b/tests/AtolOnline/Tests/Api/KktMonitorTest.php index af0836b..56b7753 100644 --- a/tests/AtolOnline/Tests/Api/KktMonitorTest.php +++ b/tests/AtolOnline/Tests/Api/KktMonitorTest.php @@ -92,14 +92,14 @@ class KktMonitorTest extends BasicTestCase */ public function testLogin(): void { - $client = new KktMonitor(login: 'login'); + $client = new KktMonitor(false, login: 'login'); $this->assertEquals('login', $client->getLogin()); $client = new KktMonitor(); - $this->assertNull($client->getLogin()); + $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin()); $client->setLogin('login'); - $this->assertEquals('login', $client->getLogin()); + $this->assertEquals(TestEnvParams::FFD105()['login'], $client->getLogin()); } /** @@ -143,14 +143,14 @@ class KktMonitorTest extends BasicTestCase */ public function testPassword(): void { - $client = new KktMonitor(password: 'password'); + $client = new KktMonitor(false, password: 'password'); $this->assertEquals('password', $client->getPassword()); $client = new KktMonitor(); - $this->assertNull($client->getPassword()); + $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword()); $client->setPassword('password'); - $this->assertEquals('password', $client->getPassword()); + $this->assertEquals(TestEnvParams::FFD105()['password'], $client->getPassword()); } /** @@ -262,7 +262,7 @@ class KktMonitorTest extends BasicTestCase * Тестирует возврат объекта последнего ответа от API * * @depends testAuth - * @covers \AtolOnline\Api\KktMonitor::getResponse + * @covers \AtolOnline\Api\KktMonitor::getLastResponse * @covers \AtolOnline\Exceptions\AuthFailedException * @throws AuthFailedException * @throws EmptyLoginException @@ -276,7 +276,7 @@ class KktMonitorTest extends BasicTestCase $this->skipIfMonitoringIsOffline(); $client = $this->newTestClient(); $client->auth(); - $this->assertIsSameClass(KktResponse::class, $client->getResponse()); + $this->assertIsSameClass(KktResponse::class, $client->getLastResponse()); } /** @@ -301,7 +301,8 @@ class KktMonitorTest extends BasicTestCase $client = $this->newTestClient(); $client->auth(); $kkts = $client->getAll(); - $this->assertNotEmpty($client->getResponse()->getContent()); + $sss = $kkts->where('deviceNumber', 'KKT014034'); + $this->assertNotEmpty($client->getLastResponse()->getContent()); $this->assertIsCollection($kkts); $this->assertTrue($kkts->count() > 0); $this->assertIsSameClass(Kkt::class, $kkts->random()); @@ -336,7 +337,7 @@ class KktMonitorTest extends BasicTestCase $client->auth(); $serial_number = $client->getAll()->first()->serialNumber; $kkt = $client->getOne($serial_number); - $this->assertNotEmpty($client->getResponse()); + $this->assertNotEmpty($client->getLastResponse()); $this->assertIsSameClass(Kkt::class, $kkt); $this->assertIsAtolable($kkt); $this->assertNotNull($kkt->serialNumber); diff --git a/tests/AtolOnline/Tests/BasicTestCase.php b/tests/AtolOnline/Tests/BasicTestCase.php index 22f1a1b..8f576d6 100644 --- a/tests/AtolOnline/Tests/BasicTestCase.php +++ b/tests/AtolOnline/Tests/BasicTestCase.php @@ -12,13 +12,27 @@ declare(strict_types = 1); 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\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; @@ -29,7 +43,7 @@ use AtolOnline\Exceptions\TooLongItemNameException; use AtolOnline\Exceptions\TooManyException; use AtolOnline\Helpers; use Exception; -use GuzzleHttp\Client; +use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Exception\GuzzleException; use Illuminate\Support\Collection; use PHPUnit\Framework\TestCase; @@ -53,7 +67,7 @@ class BasicTestCase extends TestCase protected function ping(string $url, int $code): bool { try { - $result = (new Client([ + $result = (new GuzzleClient([ 'http_errors' => false, 'timeout' => 3, ]))->request('GET', $url); @@ -387,4 +401,51 @@ class BasicTestCase extends TestCase } return $result; } + + /** + * Возвращает валидный тестовый объект чека прихода + * + * @return Receipt + * @throws EmptyItemNameException + * @throws EmptyItemsException + * @throws InvalidEntityInCollectionException + * @throws InvalidEnumValueException + * @throws NegativeItemPriceException + * @throws NegativeItemQuantityException + * @throws NegativePaymentSumException + * @throws TooHighItemPriceException + * @throws TooHighPaymentSumException + * @throws TooLongItemNameException + * @throws TooManyException + */ + protected function newReceipt(): Receipt + { + return new Receipt( + new Client('John Doe', 'john@example.com', '+79501234567', '1234567890'), + new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), + new Items($this->generateItemObjects(2)), + new Payments($this->generatePaymentObjects()) + ); + } + + /** + * Возвращает валидный тестовый объект чека + * + * @return Correction + * @throws InvalidEntityInCollectionException + * @throws InvalidEnumValueException + * @throws NegativePaymentSumException + * @throws TooHighPaymentSumException + * @throws EmptyCorrectionNumberException + * @throws InvalidCorrectionDateException + */ + protected function newCorrection(): Correction + { + return new Correction( + new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), + new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', Helpers::randomStr()), + new Payments($this->generatePaymentObjects(2)), + new Vats($this->generateVatObjects(2)), + ); + } } diff --git a/tests/AtolOnline/Tests/Entities/CorrectionTest.php b/tests/AtolOnline/Tests/Entities/CorrectionTest.php index 759cde4..7cef2b2 100644 --- a/tests/AtolOnline/Tests/Entities/CorrectionTest.php +++ b/tests/AtolOnline/Tests/Entities/CorrectionTest.php @@ -9,18 +9,18 @@ namespace AtolOnline\Tests\Entities; -use AtolOnline\{Constants\Constraints, Helpers, Tests\BasicTestCase}; -use AtolOnline\Collections\{Payments, Vats,}; -use AtolOnline\Entities\{Company, Correction, CorrectionInfo}; -use AtolOnline\Enums\{CorrectionTypes, SnoTypes}; -use AtolOnline\Exceptions\{EmptyCorrectionNumberException, +use AtolOnline\{ + Constants\Constraints, + Helpers, + Tests\BasicTestCase}; +use AtolOnline\Exceptions\{ + EmptyCorrectionNumberException, InvalidCorrectionDateException, InvalidEntityInCollectionException, InvalidEnumValueException, NegativePaymentSumException, TooHighPaymentSumException, - TooLongCashierException -}; + TooLongCashierException}; use Exception; /** @@ -114,25 +114,4 @@ class CorrectionTest extends BasicTestCase $this->expectException(TooLongCashierException::class); $this->newCorrection()->setCashier(Helpers::randomStr(Constraints::MAX_LENGTH_CASHIER_NAME + 1)); } - - /** - * Возвращает валидный тестовый объект чека - * - * @return Correction - * @throws InvalidEntityInCollectionException - * @throws InvalidEnumValueException - * @throws NegativePaymentSumException - * @throws TooHighPaymentSumException - * @throws EmptyCorrectionNumberException - * @throws InvalidCorrectionDateException - */ - protected function newCorrection(): Correction - { - return new Correction( - new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), - new CorrectionInfo(CorrectionTypes::SELF, '01.01.2021', Helpers::randomStr()), - new Payments($this->generatePaymentObjects(2)), - new Vats($this->generateVatObjects(2)), - ); - } } diff --git a/tests/AtolOnline/Tests/Entities/ReceiptTest.php b/tests/AtolOnline/Tests/Entities/ReceiptTest.php index 5665af5..ce3151b 100644 --- a/tests/AtolOnline/Tests/Entities/ReceiptTest.php +++ b/tests/AtolOnline/Tests/Entities/ReceiptTest.php @@ -127,7 +127,7 @@ class ReceiptTest extends BasicTestCase ); $receipt = $this->newReceipt()->setAgentInfo($agent_info); $this->assertArrayHasKey('agent_info', $receipt->jsonSerialize()); - $this->assertEquals($receipt->getAgentInfo(), $receipt->jsonSerialize()['agent_info']); + $this->assertEquals($receipt->getAgentInfo()->jsonSerialize(), $receipt->jsonSerialize()['agent_info']); $this->assertArrayNotHasKey('agent_info', $receipt->setAgentInfo(null)->jsonSerialize()); } @@ -159,7 +159,7 @@ class ReceiptTest extends BasicTestCase $supplier = new Supplier('some name', '+fasd3\qe3fs_=nac99013928czc', ['+122997365456']); $receipt = $this->newReceipt()->setSupplier($supplier); $this->assertArrayHasKey('supplier_info', $receipt->jsonSerialize()); - $this->assertEquals($receipt->getSupplier(), $receipt->jsonSerialize()['supplier_info']); + $this->assertEquals($receipt->getSupplier()->jsonSerialize(), $receipt->jsonSerialize()['supplier_info']); $this->assertArrayNotHasKey('supplier_info', $receipt->setSupplier(null)->jsonSerialize()); } @@ -207,7 +207,9 @@ class ReceiptTest extends BasicTestCase public function testInvalidItemInCollectionException(): void { $this->expectException(InvalidEntityInCollectionException::class); - $this->expectErrorMessage('Коллекция AtolOnline\Collections\Items должна содержать объекты AtolOnline\Entities\Item'); + $this->expectErrorMessage( + 'Коллекция AtolOnline\Collections\Items должна содержать объекты AtolOnline\Entities\Item' + ); new Receipt( new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'), new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), @@ -267,7 +269,9 @@ class ReceiptTest extends BasicTestCase public function testInvalidPaymentInCollectionException(): void { $this->expectException(InvalidEntityInCollectionException::class); - $this->expectErrorMessage('Коллекция AtolOnline\Collections\Payments должна содержать объекты AtolOnline\Entities\Payment'); + $this->expectErrorMessage( + 'Коллекция AtolOnline\Collections\Payments должна содержать объекты AtolOnline\Entities\Payment' + ); (string)new Receipt( new Client('John Doe', 'john@example.com', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'), new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), @@ -330,7 +334,9 @@ class ReceiptTest extends BasicTestCase public function testInvalidVatInCollectionException(): void { $this->expectException(InvalidEntityInCollectionException::class); - $this->expectErrorMessage('Коллекция AtolOnline\Collections\Vats должна содержать объекты AtolOnline\Entities\Vat'); + $this->expectErrorMessage( + 'Коллекция AtolOnline\Collections\Vats должна содержать объекты AtolOnline\Entities\Vat' + ); (string)$this->newReceipt()->setVats(new Vats(['qwerty'])); } @@ -357,15 +363,8 @@ class ReceiptTest extends BasicTestCase */ public function testCalculations(): void { - $items_total = 0; $receipt = $this->newReceipt(); - - //TODO при $receipt->getItems()->pluck('sum') стреляет InvalidEntityInCollectionException - // см. примечания в конструкторе EntityCollection - $receipt->getItems()->each(function ($item) use (&$items_total) { - /** @var Item $item */ - return $items_total += $item->getSum(); - }); + $items_total = $receipt->getItems()->pluck('sum')->sum(); $this->assertEquals($items_total, $receipt->getTotal()); /** @var Vat $vat */ @@ -563,34 +562,10 @@ class ReceiptTest extends BasicTestCase $aup = new AdditionalUserProps('name', 'value'); $receipt = $this->newReceipt()->setAddUserProps($aup); $this->assertArrayHasKey('additional_user_props', $receipt->jsonSerialize()); - $this->assertEquals($receipt->getAddUserProps(), $receipt->jsonSerialize()['additional_user_props']); + $this->assertEquals( + $receipt->getAddUserProps()->jsonSerialize(), + $receipt->jsonSerialize()['additional_user_props'] + ); $this->assertArrayNotHasKey('additional_user_props', $receipt->setAddUserProps(null)->jsonSerialize()); } - - /** - * Возвращает валидный тестовый объект чека - * - * @return Receipt - * @throws EmptyItemNameException - * @throws EmptyItemsException - * @throws EmptyPaymentsException - * @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', '+1/22/99*73s dsdas654 5s6', '+fasd3\qe3fs_=nac99013928czc'), - new Company('company@example.com', SnoTypes::OSN, '1234567890', 'https://example.com'), - new Items($this->generateItemObjects(2)), - new Payments($this->generatePaymentObjects()) - ); - } }