Подключение к ДеловыеЛинии

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-04-19 07:11:38 +10:00
parent d4bc3e2263
commit 7865cac5d4
18 changed files with 676 additions and 170 deletions

6
composer.lock generated
View File

@ -32,7 +32,7 @@
"version": "3.3.11", "version": "3.3.11",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/RobinHerbots/Inputmask.git", "url": "git@github.com:RobinHerbots/Inputmask.git",
"reference": "5e670ad62f50c738388d4dcec78d2888505ad77b" "reference": "5e670ad62f50c738388d4dcec78d2888505ad77b"
}, },
"dist": { "dist": {
@ -1016,7 +1016,7 @@
"source": { "source": {
"type": "git", "type": "git",
"url": "https://git.hood.su/mirzaev/yii2/arangodb", "url": "https://git.hood.su/mirzaev/yii2/arangodb",
"reference": "bb7b1b8f8e26c409f9c113a6bd459e3274e94599" "reference": "cbc26916ea54bb767306e4c2750e710b08eecc21"
}, },
"require": { "require": {
"php": "^8.0.0", "php": "^8.0.0",
@ -1058,7 +1058,7 @@
"ArangoDb", "ArangoDb",
"yii2" "yii2"
], ],
"time": "2021-04-10T16:39:51+00:00" "time": "2021-04-11T19:27:43+00:00"
}, },
{ {
"name": "mirzaev/yii2-arangodb-sessions", "name": "mirzaev/yii2-arangodb-sessions",

View File

@ -0,0 +1,20 @@
<?php
namespace app\commands;
use yii\console\Controller;
use yii\console\ExitCode;
use app\models\connection\Dellin;
class DellinController extends Controller
{
public function actionCitiesImport()
{
if (Dellin::importCities()) {
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
}

View File

@ -92,7 +92,7 @@ class NotificationController extends Controller
* @param bool $new Активация проверки на то, что уведомление не получено * @param bool $new Активация проверки на то, что уведомление не получено
* @param bool $count Посчитать * @param bool $count Посчитать
*/ */
$search = function (bool $new = false, bool $count = false) use ($model, $account, $type, $let, $limit): array|int { $search = function (bool $new = false, bool $count = false) use ($model, $account, $type, $let, $limit): array|int|null {
if ($count) { if ($count) {
// Запрошен подсчёт непрочитанных уведомлений // Запрошен подсчёт непрочитанных уведомлений

View File

@ -245,6 +245,57 @@ class ProductController extends Controller
} }
} }
public function actionEditWght(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
// Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0';
$text or $text = '0';
$product->wght = $text;
if ($product->save()) {
// Товар обновлён
$return['wght'] = $text;
}
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if ($model = Product::searchByCatn($catn)) {
return $this->render('index', compact('model'));
} else {
return $this->redirect('/');
}
}
public function actionWriteImage(string $catn): array|string|null public function actionWriteImage(string $catn): array|string|null
{ {
// Инициализация // Инициализация

View File

@ -91,10 +91,6 @@ class ProfileController extends Controller
*/ */
public function actionIndex(): string|array public function actionIndex(): string|array
{ {
$browser = new Dellin();
$browser->syncCities();
// Инициализация // Инициализация
$model = yii::$app->user->identity; $model = yii::$app->user->identity;
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
@ -122,8 +118,14 @@ class ProfileController extends Controller
} }
// Инициализация // Инициализация
$account_sections_city_list = null; $delivery_from_city_list = $model->genListCitiesFrom();
$import_sections_oem_list = $model->genListOem(Supply::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]')); $delivery_to_city_list = $model->genListCitiesTo();
$import_oem_list = $model->genListOem(Supply::searchByAccount(select: 'supply.onec["ЗначенияСвойств"]'));
// Сортировка по алфавиту
asort($delivery_from_city_list);
asort($delivery_to_city_list);
asort($import_oem_list);
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -134,8 +136,9 @@ class ProfileController extends Controller
'main' => $this->renderPartial('index', compact( 'main' => $this->renderPartial('index', compact(
'model', 'model',
'sidebar', 'sidebar',
'account_sections_city_list', 'delivery_from_city_list',
'import_sections_oem_list', 'delivery_to_city_list',
'import_oem_list',
'panel' 'panel'
)), )),
'redirect' => '/profile', 'redirect' => '/profile',
@ -146,8 +149,9 @@ class ProfileController extends Controller
return $this->render('index', compact( return $this->render('index', compact(
'model', 'model',
'sidebar', 'sidebar',
'account_sections_city_list', 'delivery_from_city_list',
'import_sections_oem_list', 'delivery_to_city_list',
'import_oem_list',
'panel' 'panel'
)); ));
} }
@ -363,7 +367,6 @@ class ProfileController extends Controller
*/ */
public function actionImport() public function actionImport()
{ {
var_dump($_FILES);
// Инициализация // Инициализация
$model = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $model = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$model->scenario = $model::SCENARIO_IMPORT_EXCEL; $model->scenario = $model::SCENARIO_IMPORT_EXCEL;

View File

@ -166,6 +166,11 @@ class SearchController extends Controller
// Инициализация // Инициализация
$row['overload'] = true; $row['overload'] = true;
} }
// Поиск аккаунтов владельцев поставок
foreach ($row['supplies'] as &$edge) {
$edge['account'] = Supply::searchAccountById($edge['_from']);
}
} }
// Запись ответа // Запись ответа

View File

@ -273,44 +273,113 @@ class Account extends Document implements IdentityInterface, PartnerInterface
// Инициализация // Инициализация
$list = []; $list = [];
// Перебор свойств поставок
foreach ($supplies as $supply) { foreach ($supplies as $supply) {
// Инициализация // Перебор поставок
if (in_array($supply['ЗначенияСвойства']['Ид'], $list, true)) {
$id = $supply['ЗначенияСвойства']['Ид'];
if (in_array($id, $list, true)) {
// Если встретился дубликат (исполняется очень часто) // Если встретился дубликат (исполняется очень часто)
continue; continue;
} }
// Генерация // Генерация
!isset($supply['ЗначенияСвойства']['Наименование']) or $list[$id] = $supply['ЗначенияСвойства']['Наименование']; empty($supply['ЗначенияСвойства']['Наименование']) or $list[$supply['ЗначенияСвойства']['Ид']] = $supply['ЗначенияСвойства']['Наименование'];
} }
// Инициализация текущего значения параметра в начале массива return $this->syncListWithSettings($list, 'import_supplies_oem');
if (isset($this->opts['import_sections_oem'])) { }
// Параметр 'import_sections_oem' найден в настройках аккаунта
if (isset($list[$this->opts['import_sections_oem']])) { /**
* Генерация списка городов из ДеловыеЛинии для отправителя
*
* Актуальное (выбранное, активное) значение записывается первым
*
* @param array Необработанный список городов
*/
public function genListCitiesFrom(): array
{
// Инициализация
$list = [];
$cities = City::readAll();
foreach ($cities as $city) {
// Перебор городов
if (in_array($city->name, $list, true)) {
// Если встретился дубликат (исполняется очень часто)
continue;
}
// Запись
empty($city->name) or $list[$city->code] = $city->name;
}
return $this->syncListWithSettings($list, 'delivery_from_city');
}
/**
* Генерация списка городов из ДеловыеЛинии для получателя
*
* Актуальное (выбранное, активное) значение записывается первым
*
* @param array Необработанный список городов
*/
public function genListCitiesTo(): array
{
// Инициализация
$list = [];
$cities = City::readAll();
foreach ($cities as $city) {
// Перебор городов
if (in_array($city->name, $list, true)) {
// Если встретился дубликат (исполняется очень часто)
continue;
}
// Запись
empty($city->name) or $list[$city->code] = $city->name;
}
return $this->syncListWithSettings($list, 'delivery_to_city');
}
/**
* Синхронизация списка вариантов параметра с текущим значением из настроек
*
* @param array &$list Список
* @param string $var Название параметра
*
* @return array Сортированный список
*/
protected function syncListWithSettings(array &$list, string $var): array {
// Инициализация текущего значения параметра в начале массива
if (isset($this->opts[$var])) {
// Параметр найден в настройках аккаунта
if (isset($list[$this->opts[$var]])) {
// Найдено совпадение сохранённого параметра с полученным списком из поставок // Найдено совпадение сохранённого параметра с полученным списком из поставок
// Буфер для сохранения параметра // Буфер для сохранения параметра
$buffer = $list[$this->opts['import_sections_oem']]; $buffer = $list[$this->opts[$var]];
// Удаление параметра // Удаление параметра
unset($list[$this->opts['import_sections_oem']]); unset($list[$this->opts[$var]]);
// Сохранение параметра в начале массива // Сохранение параметра в начале массива
$list = array_merge([$this->opts['import_sections_oem'] => $buffer], $list); $list = array_merge([$this->opts[$var] => $buffer], $list);
} else { } else {
// Совпадение не найдено // Совпадение не найдено
// Сохранение параметра из данных аккаунта в начале массива // Сохранение параметра из данных аккаунта в начале массива
$list = array_merge([$this->opts['import_sections_oem'] => $this->opts['import_sections_oem']], $list); $list = array_merge([$this->opts[$var] => $this->opts[$var]], $list);
} }
} else { } else {
// Параметр 'import_sections_oem' не найден в настройках аккаунта // Параметр $var не найден в настройках аккаунта
// Сохранение параметра из данных аккаунта в начале массива // Сохранение параметра из данных аккаунта в начале массива
$list = array_merge(['Выберите'], $list); $list = array_merge(['Выберите'], $list);

View File

@ -16,6 +16,7 @@ class City extends Document
return array_merge( return array_merge(
parent::attributes(), parent::attributes(),
[ [
'indx',
'name', 'name',
'code', 'code',
'term' 'term'
@ -29,9 +30,15 @@ class City extends Document
parent::rules(), parent::rules(),
[ [
[ [
'indx',
'string'
],
[ [
[
'indx',
'name', 'name',
'code' 'code',
'term'
], ],
'required', 'required',
'message' => 'Заполните поле: {attribute}' 'message' => 'Заполните поле: {attribute}'
@ -56,10 +63,19 @@ class City extends Document
return array_merge( return array_merge(
parent::attributeLabels(), parent::attributeLabels(),
[ [
'indx' => 'Идентификатор в ДеловыеЛинии',
'name' => 'Название', 'name' => 'Название',
'code' => 'Код КЛАДР', 'code' => 'Код КЛАДР',
'term' => 'Указатель наличия терминала в городе' 'term' => 'Указатель наличия терминала в городе'
] ]
); );
} }
/**
* Поиск по идентификатору в ДеловыеЛинии
*/
public static function searchByDellinId(string $indx): ?static
{
return static::findOne(['indx' => $indx]);
}
} }

View File

@ -70,7 +70,7 @@ abstract class Document extends ActiveRecord
$this->jrnl = array_merge( $this->jrnl = array_merge(
[[ [[
'date' => time(), 'date' => time(),
'account' => yii::$app->user->id, 'account' => yii::$app->user->id ?? 'system',
'action' => 'create' 'action' => 'create'
]], ]],
$this->jrnl ?? [] $this->jrnl ?? []
@ -106,7 +106,7 @@ abstract class Document extends ActiveRecord
[array_merge( [array_merge(
[ [
'date' => $time = time(), 'date' => $time = time(),
'account' => yii::$app->user->id, 'account' => yii::$app->user->id ?? 'system',
'action' => $action 'action' => $action
], ],
...$data ...$data

View File

@ -80,6 +80,7 @@ class Product extends Document
'dscr', 'dscr',
'prod', 'prod',
'dmns', 'dmns',
'wght',
'imgs', 'imgs',
'time' 'time'
] ]
@ -99,6 +100,7 @@ class Product extends Document
'dscr' => 'Описание (dscr)', 'dscr' => 'Описание (dscr)',
'prod' => 'Производитель (prod)', 'prod' => 'Производитель (prod)',
'dmns' => 'Габариты (dmns)', 'dmns' => 'Габариты (dmns)',
'wght' => 'Вес (wght)',
'imgs' => 'Изображения (imgs)', 'imgs' => 'Изображения (imgs)',
'time' => 'Срок доставки (time)', 'time' => 'Срок доставки (time)',
'file_excel' => 'Документ (file_excel)', 'file_excel' => 'Документ (file_excel)',
@ -301,7 +303,7 @@ class Product extends Document
// Перебор файлов // Перебор файлов
// Инициализация // Инициализация
$dir = '../assets/import/' . date('Y_m_d#H-i', time()) . '/excel/'; $dir = YII_PATH_PUBLIC . '../assets/import/' . date('Y-m-d', time()) . '/excel/' . (yii::$app->user->identity->_key ?? 'system') . '/' . time() . '/';
// Сохранение на диск // Сохранение на диск
if (!file_exists($dir)) { if (!file_exists($dir)) {

View File

@ -15,7 +15,7 @@ use carono\exchange1c\interfaces\OfferInterface;
use carono\exchange1c\interfaces\ProductInterface; use carono\exchange1c\interfaces\ProductInterface;
use carono\exchange1c\controllers\ApiController; use carono\exchange1c\controllers\ApiController;
use exception; use Exception;
/** /**
* Поставка (выгрузка товаров от поставщиков) * Поставка (выгрузка товаров от поставщиков)
@ -416,7 +416,8 @@ class Supply extends Product implements ProductInterface, OfferInterface
* @param mixed|null $context * @param mixed|null $context
* @return array * @return array
*/ */
public function getExportFields1c($context = null) { public function getExportFields1c($context = null)
{
return $this->onec; return $this->onec;
} }
@ -498,7 +499,8 @@ class Supply extends Product implements ProductInterface, OfferInterface
* @param $types * @param $types
* @return void * @return void
*/ */
public static function createPriceTypes1c($types) { public static function createPriceTypes1c($types)
{
} }
/** /**
@ -511,8 +513,8 @@ class Supply extends Product implements ProductInterface, OfferInterface
* @param \Zenwalker\CommerceML\Model\Simple $specification * @param \Zenwalker\CommerceML\Model\Simple $specification
* @return void * @return void
*/ */
public function setSpecification1c($specification) { public function setSpecification1c($specification)
{
} }
/** /**
@ -572,4 +574,43 @@ class Supply extends Product implements ProductInterface, OfferInterface
return SupplyEdgeProduct::search($this->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена']; return SupplyEdgeProduct::search($this->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена'];
} }
/**
* Найти аккаунт владельца
*
* @return Account|null Аккаунт владельца
*/
public function searchAccount(): ?array
{
return static::searchAccountById($this->_id);
}
/**
* Найти аккаунт владельца
*
* @param string|null $_id Идентификатор
*
* @return Account|null Аккаунт владельца
*/
public static function searchAccountById(string $_id): ?array
{
return static::searchByEdge(
from: 'account',
to: 'supply',
edge: 'account_edge_supply',
direction: 'INBOUND',
subquery_where: [
[
'account_edge_supply._from == account._id'
],
[
'account_edge_supply._to == "' . $_id . '"'
]
],
subquery_select: 'account',
where: 'account_edge_supply[0]._id != null',
limit: 1,
select: 'account_edge_supply[0]'
)[0];
}
} }

View File

@ -12,37 +12,38 @@ use app\models\City;
use GuzzleHttp\Client as Guzzle; use GuzzleHttp\Client as Guzzle;
use Exception; use Exception;
use phpDocumentor\Reflection\Types\Nullable;
class Dellin extends Model class Dellin extends Model
{ {
/** /**
* Инстанция браузера * Инстанция браузера
*/ */
public Guzzle $browser; public static Guzzle $browser;
/** /**
* Сессия аккаунта * Сессия аккаунта
*/ */
public string $session; public static string $session;
public function __construct($config = []) public function __construct($config = [])
{ {
parent::__construct($config); parent::__construct($config);
$this->browser = new Guzzle([ self::$browser = new Guzzle([
'base_uri' => 'https://api.dellin.ru/' 'base_uri' => 'https://api.dellin.ru/'
]); ]);
$this->authorization(); self::authorization();
} }
/** // /**
* Поиск городов // * Поиск городов
* // *
* @return array|null Найденные города // * @return array|null Найденные города
*/ // */
public function searchCities(): ?array // public function searchCities(): ?array
{ // {
// $this->onReady(function () { // $this->onReady(function () {
// // Запрос городов // // Запрос городов
// $request = $this->browser->post('/v2/public/kladr.json', [ // $request = $this->browser->post('/v2/public/kladr.json', [
@ -52,21 +53,67 @@ class Dellin extends Model
// ] // ]
// ]) // ])
// }); // });
// }
/**
* Рассчет доставки
*
* @param string $from Номер КЛАДР
* @param string $to Номер КЛАДР
*
* @return string
*/
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 Найденные города * @return array|null Найденные города
*
* @todo Написать проверку по хешу
*/ */
public function syncCities(): ?array public static function importCities(): ?int
{ {
return $this->ifReady(function () { return self::handle(function () {
// Запрос ссылки на файл с городам // Всё готово к работе
// Получаем hash и url ()
$request = $this->browser->post('/v1/public/cities.json', [ // Запрос ссылки на файл с городами, возвращает ['hash' => string, 'url' => string]
$request = self::$browser->post('/v1/public/cities.json', [
'json' => [ 'json' => [
'appkey' => yii::$app->params['dellin']['key'], 'appkey' => yii::$app->params['dellin']['key'],
] ]
@ -77,33 +124,120 @@ class Dellin extends Model
// Инициализация // Инициализация
$response = json_decode((string) $request->getBody(), true); $response = json_decode((string) $request->getBody(), true);
$hash_target = $response['hash'];
$dir = YII_PATH_PUBLIC . '/../assets/import/' . date('Y-m-d', time()) . '/dellin/cities/' . (yii::$app->user->identity->_key ?? 'system') . '/';
if (!file_exists($dir)) {
// Директории не существует
if ($response['metadata']['status'] === 200) { mkdir($dir, 0775, true);
// Аутентификация и авторизация пройдены успешно
// Запись сессии
return $this;
} }
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', $response['metadata']['status']); $request = self::$browser->get($response['url'], [
'sink' => $file = $dir . time() . '.csv'
]);
// Проверка хеша
if ($hash_target !== $hash_received = md5_file($file)) {
// Удалось пройти проверку на хеши файлов
// Инициализация (чтение файла)
$file = fopen($file, "r");
$first_raw_block = true;
while ($row = fgets($file, 4096)) {
// Перебор строк
if ($first_raw_block) {
// Сработала защита от чтения первой строки файла (указываются названия колонок)
// Отключение
$first_raw_block = false;
// Пропуск цикла
continue;
} }
// Инициализация
$data = explode(',', $row, 4);
$amount = 0;
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', $request->getStatusCode); // Очистка
array_walk($data, fn (&$value) => $value = trim($value, '"'));
if ($city = City::searchByDellinId($data[0])) {
// Удалось найти город в базе данных
$after_import_log = function () use ($city): void {
// Запись в журнал
$city->journal('update');
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось перезаписать город: ' . $city->name . PHP_EOL;
}
};
} else {
// Не удалось найти город в базе данных
$city = new City();
$after_import_log = function () use ($city): void {
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось записать город: ' . $city->name . PHP_EOL;
}
};
}
// Запись
$city->indx = $data[0];
$city->name = $data[1];
$city->code = $data[2];
$city->term = (bool) $data[3];
// Отправка в базу данных
if ($city->save()) {
// Удалось сохранить в базе данных
// Запись в журнал
$after_import_log();
// Постинкрементация счётчика
$amount++;
continue;
} else {
// Не удалось сохранить в базе данных
throw new Exception('Не удалось сохранить город ' . $data[1] . ' в базу данных', 500);
}
}
// Деинициализация
fclose($file);
return $amount;
} else {
// Не удалось пройти проверку на соответствие хешей файлов
throw new Exception('Хеши файлов не совпадают. Должен быть: "' . $hash_target . '", получен: "' . $hash_received . '"', 500);
}
}
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500);
}); });
} }
/** /**
* Аутентификация и авторизация * Аутентификация и авторизация
*/ */
protected function authorization(): ?self protected static function authorization(): bool
{ {
return $this->ifReady(function () {
// Аутентификация и авторизация // Аутентификация и авторизация
$request = $this->browser->post('/v3/auth/login.json', [ $request = self::browser()->post('/v3/auth/login.json', [
'json' => [ 'json' => [
'appkey' => yii::$app->params['dellin']['key'], 'appkey' => yii::$app->params['dellin']['key'],
'login' => yii::$app->params['dellin']['nickname'], 'login' => yii::$app->params['dellin']['nickname'],
@ -121,35 +255,56 @@ class Dellin extends Model
// Аутентификация и авторизация пройдены успешно // Аутентификация и авторизация пройдены успешно
// Запись сессии // Запись сессии
$this->session = $response['data']['sessionID']; self::$session = $response['data']['sessionID'];
return $this; return true;
} }
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $response['metadata']['status']); throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $response['metadata']['status']);
} }
throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $request->getStatusCode); throw new Exception('Не удалось авторизироваться в ДеловыеЛинии', $request->getStatusCode);
});
} }
/** /**
* Проверка на то, что браузер готов к работе * Инициализация и выполнение
* *
* @param callable $function Код к выполнению * @param callable|null $function Код к выполнению
* @param [type] ...$vars Параметры к нему * @param [mixed] ...$vars Параметры к нему
* *
* @return mixed Возврат из функции * @return mixed Возврат из функции
*/ */
protected function ifReady(callable $function, ...$vars): mixed protected static function handle(callable $function = null, mixed ...$vars): mixed
{ {
if (isset($this->browser)) { try {
if (self::browser() instanceof Guzzle) {
// Браузер инициализирован // Браузер инициализирован
return $function(...$vars); if (self::authorization() && isset(self::$session)) {
} // Аутентифицирован и авторизован
return $function(...$vars);
} else {
throw new Exception('Аккаунт не авторизирован', 401);
}
} else {
throw new Exception('Браузер не инициализирован', 500); throw new Exception('Браузер не инициализирован', 500);
} }
} catch (Exception $e) {
throw new Exception($e->getMessage(), 500, $e->getPrevious());
// throw new Exception('Не удалось инициализировать инстанцию для работы с API ДеловыеЛинии', 500, $e->getPrevious());
}
}
/**
* Чтение или инициализация браузера
*
* @return Guzzle Инстанция
*/
protected static function browser(): Guzzle
{
return self::$browser ?? self::$browser = new Guzzle([
'base_uri' => 'https://api.dellin.ru/'
]);
}
} }

View File

@ -6,13 +6,17 @@ namespace app\models\traits;
use yii; use yii;
use exception; use ArangoDBClient\Document;
use Exception;
trait SearchByEdge trait SearchByEdge
{ {
/** /**
* Поиск через связи рёбрами с аккаунтом * Поиск через связи рёбрами с аккаунтом
* *
* Аргумент $asArray и его реализацию пришлось добавить чтобы не переписывать кучу кода
*
* @param string $id Идентификатор пользователя * @param string $id Идентификатор пользователя
* @param int $limit Количество * @param int $limit Количество
* @param int $offset Сдвиг * @param int $offset Сдвиг
@ -27,12 +31,15 @@ trait SearchByEdge
int|null $offset = 0, int|null $offset = 0,
array $sort = ['ASC'], array $sort = ['ASC'],
string|array $subquery_where = [], string|array $subquery_where = [],
string|array $subquery_select = null,
array $foreach = [], array $foreach = [],
string|array $where = [], string|array $where = [],
array|null $let = [], array|null $let = [],
string|array $select = null, string|array $select = null,
callable|null $handle = null, callable|null $handle = null,
array $params = [] array $params = [],
bool $asArray = true,
bool $debug = false
): mixed { ): mixed {
$subquery = static::find() $subquery = static::find()
->params($params) ->params($params)
@ -42,7 +49,7 @@ trait SearchByEdge
->where($subquery_where); ->where($subquery_where);
$subquery = $subquery $subquery = $subquery
->select($edge ?? $from . '_edge_' . $to) ->select($subquery_select ?? $edge ?? $from . '_edge_' . $to)
->createCommand(); ->createCommand();
$query = static::find() $query = static::find()
@ -58,12 +65,20 @@ trait SearchByEdge
$query->let(...$let); $query->let(...$let);
} }
// Запрос // Инициализация
$request = $query $request = $query
->foreach($foreach) ->foreach($foreach)
->where($where) ->where($where)
->select($select ?? $to); ->select($select ?? $to);
// Режим проверки
if ($debug) {
// Запрошена проверка
return (string) $request->createCommand();
}
// Запрос
if (isset($handle)) { if (isset($handle)) {
// Передана функция для постобработки // Передана функция для постобработки
@ -71,14 +86,27 @@ trait SearchByEdge
} else if (isset($select)) { } else if (isset($select)) {
$response = $request->createCommand()->execute()->getAll(); $response = $request->createCommand()->execute()->getAll();
if ($asArray) {
// Передан параметр указывающий на необходимость возврата как объекта
// Очистка
foreach ($response as &$attribute) { foreach ($response as &$attribute) {
// Приведение всех свойств в массив и очистка от лишних данных // Приведение всех свойств в массив и очистка от лишних данных
if ($attribute instanceof Document) {
// Получена инстанция документа ArangoDB
$attribute = $attribute->getAll(); $attribute = $attribute->getAll();
} }
}
}
return $response; return $response;
} else { } else {
if ($limit === 1) {
return $request->one();
}
return $request->all(); return $request->all();
} }
} }

View File

@ -14,7 +14,7 @@ use app\models\Product;
<article class="col-12"> <article class="col-12">
<div class="p-3 d-flex flex-column rounded"> <div class="p-3 d-flex flex-column rounded">
<div id="product_slider" class="row px-3 profile_panel"> <div id="product_slider" class="row px-3 profile_panel">
<div class="col-1 product_slider_preview p-0 pr-3 mb-3"> <div class="col-1 product_slider_preview p-0 pr-3 mb-3 d-flex flex-column">
<?php <?php
// Инициализация // Инициализация
$covr_not_found = true; $covr_not_found = true;
@ -157,37 +157,40 @@ use app\models\Product;
<div class="dropdown-divider px-0 mb-2"></div> <div class="dropdown-divider px-0 mb-2"></div>
<!-- Информация о товаре -->
<?php if ( <?php if (
!yii::$app->user->isGuest !yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator' && (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator') || yii::$app->user->identity->type === 'moderator')
) : ?> ) : ?>
<section id="prod_<?= $model['catn'] ?>_info" class="col mb-1"> <section id="prod_<?= $model['catn'] ?>_info" class="col">
<p class="form-control-sm"> <!-- Габариты -->
<b class="mr-1">Габариты:</b> <p class="form-control-sm p-0 h-auto">
<span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'x');"><?= empty($model['dmns']['x']) ? '0' : $model['dmns']['x'] ?></span> <b>Габариты:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'x');"><?= empty($model['dmns']['x']) ? '0' : $model['dmns']['x'] ?></span><span class="mr-1">см</span><small>x</small><span id="prod_<?= $model['catn'] ?>_dmns_y" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'y');"><?= empty($model['dmns']['y']) ? '0' : $model['dmns']['y'] ?></span><span class="mr-1">см</span><small>x</small><span id="prod_<?= $model['catn'] ?>_dmns_z" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'z');"><?= empty($model['dmns']['z']) ? '0' : $model['dmns']['z'] ?></span><span class="mr-1">см</span>
<span class="mr-1">см</span> </p>
<small>x</small> <!-- Вес -->
<span id="prod_<?= $model['catn'] ?>_dmns_y" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'y');"><?= empty($model['dmns']['y']) ? '0' : $model['dmns']['y'] ?></span> <p class="form-control-sm p-0 h-auto">
<span class="mr-1">см</span> <b>Вес:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1 pointer-event" role="button" onclick="return product_panel_weight_edit('<?= $model['catn'] ?>', this);"><?= empty($model['wght']) ? '0' : $model['wght'] ?></span><span class="mr-1">кг</span>
<small>x</small>
<span id="prod_<?= $model['catn'] ?>_dmns_z" class="ml-1 pointer-event" role="button" onclick="return product_panel_dimensions_edit('<?= $model['catn'] ?>', this, 'z');"><?= empty($model['dmns']['z']) ? '0' : $model['dmns']['z'] ?></span>
<span class="mr-1">см</span>
</p> </p>
</section> </section>
<?php else : ?> <?php else : ?>
<section id="prod_<?= $model['catn'] ?>_info" class="col mb-1"> <section id="prod_<?= $model['catn'] ?>_info" class="col mb-1">
<p class="form-control-sm"> <!-- Габариты -->
<b class="mr-1">Габариты:</b> <p class="form-control-sm p-0 h-auto">
<span id="prod_<?= $model['catn'] ?>_dmns_x" class="mx-1"><?= $model['dmns']['x'] ?? '0' ?></span> <b>Габариты:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1"><?= empty($model['dmns']['x']) ? '0' : $model['dmns']['x'] ?></span><span class="mr-1">см</span><small>x</small><span id="prod_<?= $model['catn'] ?>_dmns_y" class="ml-1"><?= empty($model['dmns']['y']) ? '0' : $model['dmns']['y'] ?></span><span class="mr-1">см</span><small>x</small><span id="prod_<?= $model['catn'] ?>_dmns_z" class="ml-1"><?= empty($model['dmns']['z']) ? '0' : $model['dmns']['z'] ?></span><span class="mr-1">см</span>
<small>x</small> </p>
<span id="prod_<?= $model['catn'] ?>_dmns_y" class="mx-1"><?= $model['dmns']['y'] ?? '0' ?></span> <!-- Вес -->
<small>x</small> <p class="form-control-sm p-0 h-auto">
<span id="prod_<?= $model['catn'] ?>_dmns_z" class="mx-1"><?= $model['dmns']['z'] ?? '0' ?></span> <b>Габариты:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1"><?= empty($model['wght']) ? '0' : $model['wght'] ?></span><small>кг</small>
</p> </p>
</section> </section>
<?php endif ?> <?php endif ?>
<div class="dropdown-divider px-0 mb-2"></div>
<!-- Описание -->
<div class="row mb-3 h-100 product_panel d-flex flex-column"> <div class="row mb-3 h-100 product_panel d-flex flex-column">
<?php if ( <?php if (
!yii::$app->user->isGuest !yii::$app->user->isGuest

View File

@ -40,7 +40,8 @@ if (
<?php if (!yii::$app->user->isGuest) : ?> <?php if (!yii::$app->user->isGuest) : ?>
<input type="radio" id="profile_panel_settings_account" name="main_panel" <?= $panel === 'profile_panel_settings_account' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_settings_account" name="main_panel" <?= $panel === 'profile_panel_settings_account' ? 'checked' : null ?> />
<div class="col"> <div class="col">
<h5>Основная информация</h5> <h5>Доставка</h5>
<div class="dropdown-divider mb-3"></div>
<?php $form = ActiveForm::begin([ <?php $form = ActiveForm::begin([
'id' => 'form_profile_settings', 'id' => 'form_profile_settings',
'action' => false, 'action' => false,
@ -54,15 +55,15 @@ if (
// Инициализация // Инициализация
$model ?? $model = yii::$app->user->identity; $model ?? $model = yii::$app->user->identity;
$account_sections_city_list or $account_sections_city_list = ['Нет данных']; $delivery_to_city_list or $delivery_to_city_list = ['Нет данных'];
?> ?>
<?= $form->field($model, 'opts[account_sections_city]', ['options' => ['class' => "mb-1"]]) <?= $form->field($model, 'opts[delivery_to_city]', ['options' => ['class' => "mb-1"]])
->dropDownList($account_sections_city_list, [ ->dropDownList($delivery_to_city_list, [
'onChange' => 'page_profile_settings(this.parentElement.parentElement, \'profile_panel_settings_account\')' 'onChange' => 'page_profile_settings(this.parentElement.parentElement, \'profile_panel_settings_account\')'
])->label('Город'); ?> ])->label('Город'); ?>
<small class="d-block mb-1">Выберите город для <b>рассчёта доставки</b></small> <small class="d-block mb-1">Выберите город <b><u>получателя</u></b> для <b>рассчёта доставки</b></small>
<?php ActiveForm::end(); ?> <?php ActiveForm::end(); ?>
</div> </div>
@ -70,7 +71,32 @@ if (
<?php if (yii::$app->user->identity->agnt) : ?> <?php if (yii::$app->user->identity->agnt) : ?>
<input type="radio" id="profile_panel_settings_company" name="main_panel" <?= $panel === 'profile_panel_settings_company' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_settings_company" name="main_panel" <?= $panel === 'profile_panel_settings_company' ? 'checked' : null ?> />
<div class="col"> <div class="col">
2 <h5>Доставка</h5>
<div class="dropdown-divider mb-3"></div>
<?php $form = ActiveForm::begin([
'id' => 'form_profile_settings',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;'
]
]);
// Инициализация
$model ?? $model = yii::$app->user->identity;
$delivery_from_city_list or $delivery_from_city_list = ['Нет данных'];
?>
<?= $form->field($model, 'opts[delivery_from_city]', ['options' => ['class' => "mb-1"]])
->dropDownList($delivery_from_city_list, [
'onChange' => 'page_profile_settings(this.parentElement.parentElement, \'profile_panel_settings_company\')'
])->label('Город'); ?>
<small class="d-block mb-1">Выберите город <b><u>отправителя</u></b> для <b>рассчёта доставки</b></small>
<?php ActiveForm::end(); ?>
</div> </div>
<input type="radio" id="profile_panel_settings_import" name="main_panel" <?= $panel === 'profile_panel_settings_import' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_settings_import" name="main_panel" <?= $panel === 'profile_panel_settings_import' ? 'checked' : null ?> />
@ -90,13 +116,13 @@ if (
// Инициализация // Инициализация
$model ?? $model = yii::$app->user->identity; $model ?? $model = yii::$app->user->identity;
$import_sections_oem_list or $import_sections_oem_list = ['Нет данных']; $import_oem_list or $import_oem_list = ['Нет данных'];
?> ?>
<?= $form->field($model, 'opts[import_sections_oem]', ['options' => ['class' => "mb-1"]]) <?= $form->field($model, 'opts[import_supplies_oem]', ['options' => ['class' => "mb-1"]])
->dropDownList($import_sections_oem_list, [ ->dropDownList($import_oem_list, [
'onChange' => 'page_profile_settings(this.parentElement.parentElement, \'profile_panel_settings_import\')', 'onChange' => 'page_profile_settings(this.parentElement.parentElement, \'profile_panel_settings_import\')',
'disabled' => count($import_sections_oem_list) <= 1 'disabled' => count($import_oem_list) <= 1
])->label('OEM-номера'); ?> ])->label('OEM-номера'); ?>
<small class="d-block mb-1">Выберите поле в котором хранятся <b>ОЕМ-номера</b> и повторите импорт</small> <small class="d-block mb-1">Выберите поле в котором хранятся <b>ОЕМ-номера</b> и повторите импорт</small>

View File

@ -1,3 +1,11 @@
<?php
use yii;
use app\models\connection\Dellin;
?>
<link href="/css/pages/search.css" rel="stylesheet"> <link href="/css/pages/search.css" rel="stylesheet">
<div id="page_search" class="container flex-grow-1 d-flex"> <div id="page_search" class="container flex-grow-1 d-flex">
@ -72,28 +80,32 @@
// Инициализация количества // Инициализация количества
$amount_raw = $amount = $supply['amnt'] ?? $supply['onec']['Количество']; $amount_raw = $amount = $supply['amnt'] ?? $supply['onec']['Количество'];
if (empty($amount_raw) || $amount_raw < 1) { if (empty($amount_raw) || $amount_raw < 1) {
// Уже не используется
$amount = 'Под заказ'; $amount = 'Под заказ';
} else { } else {
$amount .= ' шт'; $amount .= ' шт';
} }
// Инициализация доставки
$delivery = Dellin::calcDelivery($supply['account']['opts']['delivery_from_city'], yii::$app->user->identity->opts['delivery_to_city'])['terminals_standard'];
$delivery_max = $delivery['period_to'] ?? 'Неизвестно';
$delivery_price = $delivery['price'] ?? 'Неизвестно';
if ($amount_raw < 1 || $price_raw < 1) { if ($amount_raw < 1 || $price_raw < 1) {
// Нет в наличии или цена 0 рублей // Нет в наличии или цена 0 рублей
$button_cart = <<<HTML $supplies_html .= <<<HTML
<div class="col-1 h-100 text-dark d-flex" title="Товар недоступен"> <div class="row my-auto m-0 h-100 text-right">
<a class="col-auto ml-auto my-auto text-dark" href="/order/new/custom" role="button" onclick="return false;">
<small>
Заказать поиск у оператора
</small>
</a>
</div> </div>
HTML; HTML;
} else { } else {
$button_cart = <<<HTML // Есть в наличии
<a class="col-1 h-100 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" href="/cart" role="button" onclick="return cart_write('$catn');">
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
</a>
HTML;
}
$supplies_html .= <<<HTML $supplies_html .= <<<HTML
<div class="row my-auto m-0 h-100 text-right"> <div class="row my-auto m-0 h-100 text-right">
@ -101,14 +113,18 @@
$amount $amount
</small> </small>
<small class="col-2 my-auto ml-1"> <small class="col-2 my-auto ml-1">
Доставка $delivery_max дней<br/>
$delivery_price рублей
</small> </small>
<b class="col-2 my-auto"> <b class="col-2 my-auto">
$price $price
</b> </b>
$button_cart <a class="col-1 h-100 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" href="/cart" role="button" onclick="return cart_write('$catn');">
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
</a>
</div> </div>
HTML; HTML;
}
?> ?>
<?php endforeach ?> <?php endforeach ?>

View File

@ -171,6 +171,76 @@ function product_panel_dimensions_save(catn, element, dimension) {
return true; return true;
} }
function product_panel_weight_edit(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
let input = document.createElement('input');
input.setAttribute('id', element.id);
input.setAttribute('class', 'ml-1 text-center product_options_edit_small');
input.setAttribute('type', 'number');
input.setAttribute('value', element.innerText);
input.setAttribute('aria-invalid', 'false');
element.replaceWith(input);
product_panel_handler_save(product_panel_weight_save, catn, input);
return false;
}
return true;
}
function product_panel_weight_save(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
// Инициализация
let text = element.value;
let span = document.createElement('span');
span.setAttribute('id', element.id);
span.setAttribute('class', 'ml-1 pointer-event');
span.setAttribute('role', 'button');
span.setAttribute('onclick', 'return product_panel_weight_edit(\'' + catn + '\', this);');
if (text.length === 0) {
text = '0';
}
span.innerText = text;
element.replaceWith(span);
$.ajax({
url: '/product/' + catn + '/edit/wght',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'text': text
},
success: function (data, status) {
// Заголовок
if (data.weight !== undefined && span !== null && span !== undefined) {
// Обновление заголовка
span.innerText = data.weight;
// Запись аттрибута
span.setAttribute('onclick', 'return product_panel_weight_edit(\'' + catn + '\', this);');
};
product_response_success(data, status);
},
error: product_response_error
});
return false;
}
return true;
}
function product_panel_description_edit(catn, element) { function product_panel_description_edit(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) { if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
element.innerHTML = '<textarea class="form-control" cols="50" rows="5" onchange = "return product_panel_description_save(\'' + catn + '\', this.parentElement)">' + element.innerText + '</textarea>'; element.innerHTML = '<textarea class="form-control" cols="50" rows="5" onchange = "return product_panel_description_save(\'' + catn + '\', this.parentElement)">' + element.innerText + '</textarea>';

View File

@ -10,6 +10,7 @@
defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev'); defined('YII_ENV') or define('YII_ENV', 'dev');
defined('YII_PATH_PUBLIC') or define('YII_PATH_PUBLIC', __DIR__ . '/web');
require __DIR__ . '../../../../vendor/autoload.php'; require __DIR__ . '../../../../vendor/autoload.php';
require __DIR__ . '../../../../vendor/yiisoft/yii2/Yii.php'; require __DIR__ . '../../../../vendor/yiisoft/yii2/Yii.php';