This repository has been archived on 2024-10-16. You can view files and clone it, but cannot push or open issues or pull requests.
skillparts/mirzaev/skillparts/system/models/connection/Dellin.php

490 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace app\models\connection;
use yii;
use yii\base\Model;
use app\models\Dellin as DellinModel;
use app\models\Product;
use app\models\Account;
use app\models\Settings;
use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Exception\ClientException as GuzzleException;
use DateTime;
use DateTimeZone;
use Exception;
class Dellin extends Model
{
/**
* Инстанция браузера
*/
public static Guzzle $browser;
/**
* Сессия аккаунта
*/
public static string $session;
public function __construct($config = [])
{
parent::__construct($config);
self::$browser = new Guzzle([
'base_uri' => 'https://api.dellin.ru/'
]);
self::authorization();
}
// /**
// * Поиск городов
// *
// * @return array|null Найденные города
// */
// public function searchCities(): ?array
// {
// $this->onReady(function () {
// // Запрос городов
// $request = $this->browser->post('/v2/public/kladr.json', [
// 'json' => [
// 'appkey' => yii::$app->params['dellin']['key'],
// ]
// ])
// });
// }
/**
* Рассчет доставки (расширенный)
*
* Рассчет нескольких товаров идет через простое перемножение результатов доставки одного товара
* В API всегда идет рассчет для одного товара, так было решено
*
* @param int $from Идентификатор терминала Dellin
* @param int $to Идентификатор терминала Dellin
* @param int $weight Вес (кг)
* @param int $x Ширина (cм)
* @param int $y Высота (cм)
* @param int $z Длинна (cм)
* @param int $amount Количество
* @param Account|int|null $account Аккаунт
*
* @return string
*
* @todo Загружать помимо терминалов ещё и адреса, чтобы доделать доставку малогабаритных грузов
* Разрабраться с параметрами 0,54м * 0,39м * 0,39м (0.082134м) и 0.1 куб метр в чем разница
*/
public static function calcDeliveryAdvanced(int $from, int $to, int $weight, int $x, int $y, int $z, int $amount = 1, bool $avia = false, Account|int|null $account = null): array
{
return self::handle(function () use ($from, $to, $weight, $x, $y, $z, $amount, $avia, $account) {
// Всё готово к работе
$account = Account::initAccount($account);
// Инициализация
$from = DellinModel::searchByTerminalId($from, terminal_data_only: true);
$to = DellinModel::searchByTerminalId($to, terminal_data_only: true);
// Значения по умолчанию, если указан 0
if (empty($x) || $x === 0) $x = 25;
if (empty($y) || $y === 0) $y = 40;
if (empty($z) || $z === 0) $z = 25;
if (empty($weight) || $weight === 0) $weight = 300;
// Конвертация из сантиметров в метры
$x /= 100;
$y /= 100;
$z /= 100;
// Вычисление самой крупной стороны, так как ДеловыеЛинии имеют ограничения на все три поля и у длинны оно больше всех
if ($x > $z && $x > $y) {
// "X" больше всех
// Инициализация
$width = $z;
$height = $y;
$length = $x;
} else if ($y > $x && $y > $z) {
// "Y" больше всех
// Инициализация
$width = $x;
$height = $z;
$length = $y;
} else {
// "Z" больше всех
// Инициализация
$width = $x;
$height = $y;
$length = $z;
}
// Инициализация
$query = [];
// Рассчёт типа доставки
if (
!$avia
&& $weight <= 30
&& ($length <= 0.54 && $width <= 0.39 && $height <= 0.39)
&& $length * $width * $height <= 0.1
) {
// Доставка категории "small"
$query['delivery']['deliveryType']['type'] = 'small';
$query['delivery']['derival']['variant'] = 'address';
$query['delivery']['derival']['address']['search'] = $from->fullAddress;
$query['delivery']['derival']['time']['worktimeStart'] = '08:00';
$query['delivery']['derival']['time']['worktimeEnd'] = '20:00';
$query['delivery']['arrival']['variant'] = 'address';
$query['delivery']['arrival']['address']['search'] = $to->fullAddress;
$query['delivery']['arrival']['time']['worktimeStart'] = '08:00';
$query['delivery']['arrival']['time']['worktimeEnd'] = '20:00';
} else {
// Доставка категории "auto"
if ($avia) {
// Рассчет для доставки по воздуху
// Ограничение на минимальный вес
$weight <= 0.5 and $weight = 0.5;
$query['delivery']['deliveryType']['type'] = 'avia';
} else {
// Рассчет для доставки по земле
$query['delivery']['deliveryType']['type'] = 'auto';
}
$query['delivery']['derival']['variant'] = 'terminal';
$query['delivery']['derival']['terminalID'] = $from->id;
$query['delivery']['arrival']['variant'] = 'terminal';
$query['delivery']['arrival']['terminalID'] = $to->id;
}
// Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
// Инициализация
$query = array_merge_recursive(
$query,
[
'appkey' => yii::$app->params['dellin']['key'],
'sessionID' => self::$session,
'delivery' => [
'derival' => [
'produceDate' => (new DateTime())->setTimestamp(time() + 86400 * 3)->setTimezone(new DateTimeZone($timezone))->format('Y-m-d')
]
],
'members' => [
'requester' => [
'role' => 'sender'
]
],
'cargo' => [
'quantity' => 1,
'width' => $width,
'height' => $height,
'length' => $length,
'totalVolume' => $width * $height * $length,
'totalWeight' => $weight,
'oversizedWeight' => $weight,
'oversizedVolume' => $width * $height * $length
]
]
);
// Запрос
$request = self::$browser->post('/v2/calculator.json', [
'json' => $query
]);
if ($request->getStatusCode() === 200) {
// Запрос прошел успешно
// Инициализация
$response = json_decode((string) $request->getBody(), true);
if ($response['metadata']['status'] === 200) {
// Со стороны ДеловыеЛинии ошибок нет
$response['data']['price'] = [
'one' => $response['data']['price'],
'all' => $response['data']['price'] * $amount
];
return $response['data'];
}
throw new Exception('На стороне сервера ДеловыеЛинии какие-то проблемы, либо отправлен неверный запрос', 500);
}
throw new Exception('Не удалось запросить рассчёт доставки у ДеловыеЛинии', 500);
});
}
/**
* Рассчет доставки
*
* @param string $from Идентификатор терминала Dellin
* @param string $to Идентификатор терминала Dellin
*
* @return string
*
* @deprecated
*/
public static function calcDelivery(string $from, string $to): array
{
return self::handle(function () use ($from, $to) {
// Всё готово к работе
// Запрос
$request = self::$browser->post('/v1/micro_calc.json', [
'json' => [
'appkey' => yii::$app->params['dellin']['key'],
'sessionID' => self::$session,
'derival' => [
'city' => $from
],
'arrival' => [
'city' => $to
]
]
]);
if ($request->getStatusCode() === 200) {
// Запрос прошел успешно
// Инициализация
$response = json_decode((string) $request->getBody(), true);
if ($response['metadata']['status'] === 200) {
// Со стороны ДеловыеЛинии ошибок нет
return $response['data'];
}
throw new Exception('На стороне сервера ДеловыеЛинии какие-то проблемы, либо отправлен неверный запрос', 500);
}
throw new Exception('Не удалось запросить рассчёт доставки у ДеловыеЛинии', 500);
});
}
/**
* Импорт терминалов
*
* @return array|null Сохранённые терминалы
*/
public static function importTerminals(Account|int|null $account = null): ?int
{
return self::handle(function () use ($account) {
// Всё готово к работе
if (is_null($account)) {
// Данные аккаунта не переданы
if (isset(yii::$app->user)) {
if (yii::$app->user->isGuest) {
// Аккаунт не аутентифицирован
return 0;
} else {
// Аккаунт аутентифицирован
// Инициализация
$account = yii::$app->user->identity;
}
} else {
return 0;
}
} else {
if (is_int($account)) {
// Передан идентификатор (_key) аккаунта (подразумевается)
// Инициализация (поиск в базе данных)
if (!$account = Account::searchById(Account::collectionName() . "/$account")) {
// Не удалось инициализировать аккаунт
return 0;
}
}
}
// Запрос ссылки на файл с городами, возвращает ['hash' => string, 'url' => string]
$request = self::$browser->post('/v3/public/terminals.json', [
'json' => [
'appkey' => yii::$app->params['dellin']['key'],
]
]);
if ($request->getStatusCode() === 200) {
// Запрос прошел успешно
// Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
// Инициализация параметров
$response = json_decode((string) $request->getBody(), true);
$dir = YII_PATH_PUBLIC . '/../assets/import/' . (new DateTime('now', new DateTimeZone($timezone)))->format('Y-m-d') . '/dellin/terminals/' . (yii::$app->user->identity->_key ?? 'system') . '/';
$amount = 0;
if (!file_exists($dir)) {
// Директории не существует
mkdir($dir, 0775, true);
}
$request = self::$browser->get($response['url'], [
'sink' => $file = $dir . time() . '.json'
]);
// Инициализация
$terminals = json_decode(fread(fopen($file, "r"), filesize($file)), true);
foreach ($terminals['city'] as $terminal) {
// Перебор городов
if ($model = DellinModel::searchByCityId($terminal['id'])) {
// Удалось найти город в базе данных
$after_import_log = function () use ($model): void {
// Запись в журнал
$model->journal('update');
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось перезаписать терминалы города: ' . ($model->data['name'] ?? 'Неизвестно') . PHP_EOL;
}
};
} else {
// Не удалось найти город в базе данных
$model = new DellinModel();
$after_import_log = function () use ($model): void {
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось записать терминалы города: ' . ($model->data['name'] ?? 'Неизвестно') . PHP_EOL;
}
};
}
// Запись
$model->data = $terminal;
// Отправка в базу данных
if ($model->save()) {
// Удалось сохранить в базе данных
// Запись в журнал
$after_import_log();
// Постинкрементация счётчика
$amount++;
continue;
} else {
// Не удалось сохранить в базе данных
throw new Exception('Не удалось сохранить терминалы города "' . ($model->data['name'] ?? 'Неизвестно') . '" в базу данных', 500);
}
}
return $amount;
}
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500);
});
}
/**
* Аутентификация и авторизация
*/
protected static function authorization(): bool
{
// Аутентификация и авторизация
$request = self::browser()->post('/v3/auth/login.json', [
'json' => [
'appkey' => yii::$app->params['dellin']['key'],
'login' => yii::$app->params['dellin']['nickname'],
'password' => yii::$app->params['dellin']['password']
]
]);
if ($request->getStatusCode() === 200) {
// Запрос прошел успешно
// Инициализация
$response = json_decode((string) $request->getBody(), true);
if ($response['metadata']['status'] === 200) {
// Аутентификация и авторизация пройдены успешно
// Запись сессии
self::$session = $response['data']['sessionID'];
return true;
}
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $response['metadata']['status']);
}
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $request->getStatusCode);
}
/**
* Инициализация и выполнение
*
* @param callable|null $function Код к выполнению
* @param [mixed] ...$vars Параметры к нему
*
* @return mixed Возврат из функции
*/
protected static function handle(callable $function = null, mixed ...$vars): mixed
{
try {
if (self::browser() instanceof Guzzle) {
// Браузер инициализирован
if (self::authorization() && isset(self::$session)) {
// Аутентифицирован и авторизован
return $function(...$vars);
} else {
throw new Exception('Аккаунт не авторизирован', 401);
}
} else {
throw new Exception('Браузер не инициализирован', 500);
}
} catch (GuzzleException $e) {
throw new Exception($e->getResponse()->getBody()->getContents() ?? 'Не удалось инициализировать инстанцию для работы с API ДеловыеЛинии', 500, $e->getPrevious());
} catch (Exception $e) {
throw new Exception($e->getMessage() ?? 'Не удалось инициализировать инстанцию для работы с API ДеловыеЛинии', 500, $e->getPrevious());
}
}
/**
* Чтение или инициализация браузера
*
* @return Guzzle Инстанция
*/
protected static function browser(): Guzzle
{
return self::$browser ?? self::$browser = new Guzzle([
'base_uri' => 'https://api.dellin.ru/'
]);
}
}