%PDF- %PDF-
Server IP : 37.220.80.31 / Your IP : 3.149.237.89 Web Server : Apache/2.4.52 (Ubuntu) System : Linux 3051455-guretool.twc1.net 5.15.0-107-generic #117-Ubuntu SMP Fri Apr 26 12:26:49 UTC 2024 x86_64 User : www-root ( 1010) PHP Version : 7.4.33 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/www-root/data/www/dev.artlot24.ru/bitrix/modules/sale/lib/cashbox/ |
Upload File : |
<?php namespace Bitrix\Sale\Cashbox; use Bitrix\Main\Web\HttpClient; use Bitrix\Sale\PriceMaths; use Bitrix\Sale\Result; use Bitrix\Sale\Helpers\Admin\Product; use Bitrix\Main; use Bitrix\Main\Localization\Loc; use Bitrix\Sale\ResultWarning; use Bitrix\Catalog; Loc::loadMessages(__FILE__); /** * Class CashboxCheckbox * @package Bitrix\Sale\Cashbox */ class CashboxCheckbox extends Cashbox implements IPrintImmediately, ICheckable { private const HANDLER_MODE_TEST = 'TEST'; private const HANDLER_MODE_ACTIVE = 'ACTIVE'; private const API_VERSION = 'v1'; private const HANDLER_TEST_URL = 'https://dev-api.checkbox.in.ua/api'; private const HANDLER_ACTIVE_URL = 'https://api.checkbox.in.ua/api'; private const OPERATION_SIGN_IN = 'cashier/signin'; private const OPERATION_CREATE_SHIFT = 'shifts'; private const OPERATION_CHECK_SHIFTS = 'cashier/shift'; private const OPERATION_CLOSE_SHIFT = 'shifts/close'; private const OPERATION_CREATE_CHECK = 'receipts/sell'; private const OPERATION_GET_CHECK = 'receipts'; private const MAX_CODE_LENGTH = 256; private const MAX_NAME_LENGTH = 256; private const HTTP_METHOD_GET = 'get'; private const HTTP_METHOD_POST = 'post'; private const HTTP_NO_REDIRECT = false; private const HTTP_RESPONSE_CODE_201 = 201; private const HTTP_RESPONSE_CODE_202 = 202; private const HTTP_RESPONSE_CODE_400 = 400; private const HTTP_RESPONSE_CODE_401 = 401; private const HTTP_RESPONSE_CODE_403 = 403; private const HTTP_RESPONSE_CODE_422 = 422; private const SHIFT_STATUS_OPENED = 'OPENED'; private const CHECK_STATUS_DONE = 'DONE'; private const CHECK_STATUS_ERROR = 'ERROR'; private const HEADER_TOKEN_TYPE = 'Bearer'; private const TOKEN_OPTION_NAME = 'cashbox_checkbox_token'; private const QUANTITY_MULTIPLIER = 1000; private const PRICE_MULTIPLIER = 100; private const DPS_URL = 'https://cabinet.tax.gov.ua/cashregs/check?'; private const CODE_NO_VAT = '0'; private const CODE_VAT_0 = '4'; private const CODE_VAT_7 = '2'; private const CODE_VAT_20 = '1'; private const OPEN_SHIFT_WAIT_SECONDS = 5; private const OPEN_SHIFT_WAIT_ATTEMPTS = 2; private const BITRIX_CLIENT_NAME = 'api_1c-bitrix'; public static function getName() { return Loc::getMessage('SALE_CASHBOX_CHECKBOX_TITLE'); } private static function isCheckReturn(Check $check) { return ($check instanceof SellReturnCheck || $check instanceof SellReturnCashCheck); } public function buildCheckQuery(Check $check) { $checkData = $check->getDataForCheck(); $isReturn = self::isCheckReturn($check); $goods = []; foreach ($checkData['items'] as $item) { $goodEntry = []; $itemId = $item['entity']->getField('PRODUCT_ID'); $code = $item['properties']['ARTNUMBER']; if (!$code) { $code = $itemId; } if (!$code) { $code = 'delivery' . $item['entity']->getField('ID'); } $vat = $this->getValueFromSettings('VAT', $item['vat']); $goodEntry['good'] = [ 'code' => mb_substr($code, 0, static::MAX_CODE_LENGTH), 'name' => mb_substr($item['name'], 0, static::MAX_NAME_LENGTH), 'price' => PriceMaths::roundPrecision($item['price'] * static::PRICE_MULTIPLIER), ]; if ($vat && $vat !== static::CODE_NO_VAT) { $goodEntry['good']['tax'] = [$vat]; } if ($item['barcode']) { $goodEntry['good']['barcode'] = $item['barcode']; } $goodEntry['quantity'] = $item['quantity'] * static::QUANTITY_MULTIPLIER; $goodEntry['is_return'] = $isReturn; $goods[] = $goodEntry; } $delivery = []; if ($checkData['client_email']) { $delivery['email'] = $checkData['client_email']; } $payments = []; foreach ($checkData['payments'] as $payment) { $paymentType = $payment['type'] === Check::PAYMENT_TYPE_CASH ? 'CASH' : 'CARD'; $paymentEntry = [ 'type' => $paymentType, 'value' => PriceMaths::roundPrecision($payment['sum'] * static::PRICE_MULTIPLIER), ]; $payments[] = $paymentEntry; } $result = [ 'goods' => $goods, 'delivery' => $delivery, 'payments' => $payments, ]; return $result; } /** * @inheritDoc */ public function buildZReportQuery($id) { return []; } /** * @inheritDoc */ public function check(Check $check) { $url = $this->getRequestUrl(static::OPERATION_GET_CHECK, ['CHECK_ID' => $check->getField('EXTERNAL_UUID')]); $token = $this->getAccessToken(); $requestHeaders = [ 'ACCESS_TOKEN' => $token, ]; $requestBody = []; $checkResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_GET, $url, $requestHeaders, $requestBody); if (!$checkResult->isSuccess()) { return $checkResult; } $response = $checkResult->getData(); $responseStatus = $response['status']; switch ($responseStatus) { case static::CHECK_STATUS_DONE: return static::applyCheckResult($response); case static::CHECK_STATUS_ERROR: $checkResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR'))); return $checkResult; default: return new Result(); } } protected static function extractCheckData(array $data) { $result = []; if (!isset($data['id'])) { return $result; } $checkInfo = CheckManager::getCheckInfoByExternalUuid($data['id']); $result['ID'] = $checkInfo['ID']; $result['CHECK_TYPE'] = $checkInfo['TYPE']; $check = CheckManager::getObjectById($checkInfo['ID']); $dateTime = new Main\Type\DateTime($data['fiscal_date'], 'Y-m-d\TH:i:s.u'); $result['LINK_PARAMS'] = [ Check::PARAM_REG_NUMBER_KKT => $data['shift']['cash_register']['id'], Check::PARAM_FISCAL_DOC_NUMBER => $data['fiscal_code'], Check::PARAM_FN_NUMBER => $data['shift']['cash_register']['fiscal_number'], Check::PARAM_SHIFT_NUMBER => $data['shift']['serial'], Check::PARAM_DOC_SUM => (float)$checkInfo['SUM'], Check::PARAM_DOC_TIME => $dateTime->getTimestamp(), Check::PARAM_CALCULATION_ATTR => $check::getCalculatedSign() ]; return $result; } /** * @inheritDoc */ public function printImmediately(Check $check) { $url = $this->getRequestUrl(static::OPERATION_CREATE_CHECK); $token = $this->getAccessToken(); $requestHeaders = [ 'ACCESS_TOKEN' => $token, ]; $requestBody = $this->buildCheckQuery($check); $printResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody, self::HTTP_NO_REDIRECT); if (!$printResult->isSuccess()) { return $printResult; } $response = $printResult->getData(); if ($response['http_code'] === self::HTTP_RESPONSE_CODE_400) { $openShiftResult = $this->openShift(); if (!$openShiftResult->isSuccess()) { return $openShiftResult; } $this->addCloseShiftAgent(); $printResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody, self::HTTP_NO_REDIRECT); if (!$printResult->isSuccess()) { return $printResult; } $response = $printResult->getData(); } $responseCode = $response['http_code']; switch ($responseCode) { case self::HTTP_RESPONSE_CODE_201: if ($response['id']) { $printResult->setData(['UUID' => $response['id']]); } else { $printResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR'))); } break; case self::HTTP_RESPONSE_CODE_422: if ($response['detail']) { foreach ($response['detail'] as $errorDetail) { $printResult->addError(new Main\Error($errorDetail['msg'])); } } else { $printResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR'))); } break; default: if ($response['message']) { $printResult->addError(new Main\Error($response['message'])); } else { $printResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_CHECK_PRINT_ERROR'))); } } return $printResult; } private function addCloseShiftAgent() { $agentName = 'Bitrix\Sale\Cashbox\CashboxCheckbox::closeShiftAgent(' . $this->getField('ID') .');'; $agentTime = Main\Type\DateTime::createFromPhp(date_create('today 23:50')); \CAgent::AddAgent($agentName, 'sale', 'Y', 0, '', 'Y', $agentTime); } /** * @param $cashboxId * @throws Main\ArgumentNullException * @throws Main\ArgumentOutOfRangeException * @throws Main\ObjectException */ public static function closeShiftAgent($cashboxId) { $cashbox = Manager::getObjectById($cashboxId); if ($cashbox && $cashbox instanceof self) { $closeShiftResult = $cashbox->closeShift(); if (!$closeShiftResult->isSuccess()) { $closeShiftErrors = $closeShiftResult->getErrorCollection(); foreach ($closeShiftErrors as $error) { if ($error instanceof Errors\Warning) { Logger::addWarning($error->getMessage(), $cashbox->getField('ID')); } else { Logger::addError($error->getMessage(), $cashbox->getField('ID')); } } } } } private function sendRequest(string $method, string $url, array $headersData = [], array $bodyData = [], bool $allowRedirect = true): Result { $result = new Result(); $requestHeaders = static::getHeaders($headersData); $requestBody = static::encode($bodyData); $httpClient = new HttpClient(); $httpClient->setRedirect($allowRedirect); $httpClient->setHeaders($requestHeaders); if ($method === self::HTTP_METHOD_POST) { $response = $httpClient->post($url, $requestBody); } else { $response = $httpClient->get($url); } if ($response) { $responseData = static::decode($response); $responseData['http_code'] = $httpClient->getStatus(); $result->addData($responseData); } else { $error = $httpClient->getError(); foreach ($error as $code =>$message) { $result->addError(new Main\Error($message, $code)); } } return $result; } private function sendRequestWithAuthorization(string $method, string $url, array $headersData = [], array $bodyData = [], bool $allowRedirect = true): Result { $firstRequestResult = $this->sendRequest($method, $url, $headersData, $bodyData, $allowRedirect); if (!$firstRequestResult->isSuccess()) { return $firstRequestResult; } $firstRequestResponse = $firstRequestResult->getData(); $badResponseCodes = [self::HTTP_RESPONSE_CODE_401, self::HTTP_RESPONSE_CODE_403]; if (!in_array($firstRequestResponse['http_code'], $badResponseCodes)) { return $firstRequestResult; } $headersDataWithNewToken = $headersData; $requestTokenResult = $this->requestAccessToken(); if (!$requestTokenResult->isSuccess()) { return $requestTokenResult; } $newToken = $requestTokenResult->get('token'); $headersDataWithNewToken['ACCESS_TOKEN'] = $newToken; $secondRequestResult = $this->sendRequest($method, $url, $headersDataWithNewToken, $bodyData, $allowRedirect); return $secondRequestResult; } private static function getAuthorizationHeaderValue(string $token): ?string { if ($token) { return static::HEADER_TOKEN_TYPE . ' ' . $token; } return null; } private static function getHeaders(array $headersData): array { $accessToken = $headersData['ACCESS_TOKEN'] ?? ''; return [ 'Authorization' => static::getAuthorizationHeaderValue($accessToken), 'X-License-Key' => $headersData['LICENSE_KEY'], 'X-Client-Name' => static::BITRIX_CLIENT_NAME, 'X-Client-Version' => '1.0', ]; } private static function encode(array $data) { return Main\Web\Json::encode($data, JSON_UNESCAPED_UNICODE); } private static function decode(string $data) { return Main\Web\Json::decode($data); } private function getRequestUrl(string $action, array $requestParams = []): string { $url = static::HANDLER_ACTIVE_URL; if ($this->getValueFromSettings('INTERACTION', 'HANDLER_MODE') === self::HANDLER_MODE_TEST) { $url = static::HANDLER_TEST_URL; } $url .= '/' . static::API_VERSION; switch ($action) { case static::OPERATION_CREATE_SHIFT: $url .= '/' . static::OPERATION_CREATE_SHIFT; break; case static::OPERATION_CHECK_SHIFTS: $url .= '/' . static::OPERATION_CHECK_SHIFTS; break; case static::OPERATION_CLOSE_SHIFT: $url .= '/' . static::OPERATION_CLOSE_SHIFT; break; case static::OPERATION_CREATE_CHECK: $url .= '/' . static::OPERATION_CREATE_CHECK; break; case static::OPERATION_GET_CHECK: $url .= '/' . static::OPERATION_GET_CHECK . '/' . $requestParams['CHECK_ID']; break; case static::OPERATION_SIGN_IN: $url .= '/' . static::OPERATION_SIGN_IN; break; default: throw new Main\SystemException(); } return $url; } private function getTokenOptionName(): string { return static::TOKEN_OPTION_NAME . '_' . $this->getField('ID'); } private function getAccessToken(): string { $optionName = $this->getTokenOptionName(); return Main\Config\Option::get('sale', $optionName, ''); } private function setAccessToken(string $token): void { $optionName = $this->getTokenOptionName(); Main\Config\Option::set('sale', $optionName, $token); } private function requestAccessToken(): Result { $result = new Result(); $url = $this->getRequestUrl(static::OPERATION_SIGN_IN); $requestData = [ 'login' => $this->getValueFromSettings('AUTH', 'LOGIN'), 'password' => $this->getValueFromSettings('AUTH', 'PASSWORD'), ]; $headersData = []; $requestResult = $this->sendRequest(self::HTTP_METHOD_POST, $url, $headersData, $requestData); if (!$requestResult->isSuccess()) { return $requestResult; } $response = $requestResult->getData(); if ($response['http_code'] === self::HTTP_RESPONSE_CODE_403) { $result->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_AUTHORIZATION_ERROR'))); return $result; } if ($response['access_token']) { $token = $response['access_token']; $this->setAccessToken($token); $result->set('token', $token); return $result; } $result->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_TOKEN_ERROR'))); return $result; } private function getCurrentShift(): Result { $url = $this->getRequestUrl(static::OPERATION_CHECK_SHIFTS); $token = $this->getAccessToken(); $requestHeaders = [ 'ACCESS_TOKEN' => $token, ]; $requestBody = []; $result = $this->sendRequestWithAuthorization(self::HTTP_METHOD_GET, $url, $requestHeaders, $requestBody); return $result; } private function openShift(): Result { $url = $this->getRequestUrl(static::OPERATION_CREATE_SHIFT); $token = $this->getAccessToken(); $requestHeaders = [ 'ACCESS_TOKEN' => $token, 'LICENSE_KEY' => $this->getValueFromSettings('AUTH', 'LICENSE_KEY'), ]; $requestBody = []; $openShiftResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody); if (!$openShiftResult->isSuccess()) { return $openShiftResult; } $response = $openShiftResult->getData(); switch ($response['http_code']) { case self::HTTP_RESPONSE_CODE_202: $waitAttempts = 0; $openShiftSuccess = false; while ($waitAttempts < static::OPEN_SHIFT_WAIT_ATTEMPTS) { sleep(static::OPEN_SHIFT_WAIT_SECONDS); $currentShiftResult = $this->getCurrentShift(); if (!$currentShiftResult->isSuccess()) { return $currentShiftResult; } $currentShiftStatus = $currentShiftResult->getData()['status']; if ($currentShiftStatus === static::SHIFT_STATUS_OPENED) { $openShiftSuccess = true; break; } $waitAttempts++; } if (!$openShiftSuccess) { $openShiftResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR'))); return $openShiftResult; } return $openShiftResult; case self::HTTP_RESPONSE_CODE_400: $currentShiftResult = $this->getCurrentShift(); if (!$currentShiftResult->isSuccess()) { return $currentShiftResult; } $currentShift = $currentShiftResult->getData(); if ($currentShift['status'] && $currentShift['status'] === static::SHIFT_STATUS_OPENED) { $openShiftResult->addWarning(new ResultWarning(Loc::getMessage('SALE_CASHBOX_CHECKBOX_SHIFT_ALREADY_OPENED'))); return $openShiftResult; } $openShiftResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR'))); return $openShiftResult; default: $openShiftResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_SHIFT_OPEN_ERROR'))); return $openShiftResult; } } private function closeShift(): Result { $url = $this->getRequestUrl(static::OPERATION_CLOSE_SHIFT); $token = $this->getAccessToken(); $requestHeaders = [ 'ACCESS_TOKEN' => $token, ]; $requestBody = []; $closeShiftResult = $this->sendRequestWithAuthorization(self::HTTP_METHOD_POST, $url, $requestHeaders, $requestBody); if (!$closeShiftResult->isSuccess()) { return $closeShiftResult; } $response = $closeShiftResult->getData(); if ($response['http_code'] !== self::HTTP_RESPONSE_CODE_202) { $closeShiftResult->addError(new Main\Error(Loc::getMessage('SALE_CASHBOX_CHECKBOX_SHIFT_CLOSE_ERROR'))); } return $closeShiftResult; } /** * @inheritDoc */ public static function getSettings($modelId = 0) { $settings = [ 'AUTH' => [ 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH'), 'REQUIRED' => 'Y', 'ITEMS' => [ 'LOGIN' => [ 'TYPE' => 'STRING', 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_LOGIN_LABEL'), ], 'PASSWORD' => [ 'TYPE' => 'STRING', 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_PASSWORD_LABEL'), ], 'LICENSE_KEY' => [ 'TYPE' => 'STRING', 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_AUTH_LICENSE_KEY_LABEL'), ], ], ], 'INTERACTION' => [ 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_INTERACTION'), 'ITEMS' => [ 'HANDLER_MODE' => [ 'TYPE' => 'ENUM', 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_HANDLER_MODE_LABEL'), 'OPTIONS' => [ self::HANDLER_MODE_ACTIVE => Loc::getMessage('SALE_CASHBOX_CHECKBOX_MODE_ACTIVE'), self::HANDLER_MODE_TEST => Loc::getMessage('SALE_CASHBOX_CHECKBOX_MODE_TEST'), ], ], ], ], ]; $settings['VAT'] = [ 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_VAT'), 'REQUIRED' => 'Y', 'ITEMS' => [ 'NOT_VAT' => [ 'TYPE' => 'STRING', 'LABEL' => Loc::getMessage('SALE_CASHBOX_CHECKBOX_SETTINGS_VAT_LABEL_NOT_VAT'), 'VALUE' => static::CODE_NO_VAT, ] ] ]; if (Main\Loader::includeModule('catalog')) { $dbRes = Catalog\VatTable::getList(['filter' => ['ACTIVE' => 'Y']]); $vatList = $dbRes->fetchAll(); if ($vatList) { $defaultVatList = [ 0 => static::CODE_VAT_0, 7 => static::CODE_VAT_7, 20 => static::CODE_VAT_20, ]; foreach ($vatList as $vat) { $value = $defaultVatList[(int)$vat['RATE']] ?? ''; $settings['VAT']['ITEMS'][(int)$vat['ID']] = [ 'TYPE' => 'STRING', 'LABEL' => $vat['NAME'].' ['.(int)$vat['RATE'].'%]', 'VALUE' => $value ]; } } } return $settings; } /** * @inheritDoc */ public function getCheckLink(array $linkParams) { $queryParams = [ 'id=' . $linkParams[Check::PARAM_FISCAL_DOC_NUMBER], 'fn=' . $linkParams[Check::PARAM_FN_NUMBER], 'date=' . date("Y-m-d",$linkParams[Check::PARAM_DOC_TIME]), ]; return static::DPS_URL.implode('&', $queryParams); } }