Псевдоанонимные идентификаторы аккаунтов и комментарий к заказу

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-04-25 20:00:05 +10:00
parent ada91bd0b7
commit 0a8c74a9a3
17 changed files with 764 additions and 186 deletions

View File

@ -0,0 +1,36 @@
<?php
namespace app\commands;
use yii\console\Controller;
use yii\console\ExitCode;
use app\models\Account;
class AccountController extends Controller
{
/**
* Сгенерировать уникальные идентификаторы
*
* @param int|string $_key Ключ аккаунта (оставить пустым или отправить "all", если для всех)
* @param bool $init Параметр обозначающий изменение только для тех у кого ранее идентификатор задан не был (без перезаписи)
*/
public function actionGenerateIndex(int|string $_key = null, bool $init = true)
{
// Инициализация
$accounts = empty($_key) || strcasecmp($_key, 'all') === 0 ? Account::readAll() : [Account::searchById($_key)];
// Генерация
$amount = Account::generateIndexes($accounts, $init);
echo 'Обработано аккаунтов: ' . $amount;
if ($amount > 0) {
// Был успешно обработан минимум 1 аккаунт
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
}

View File

@ -9,7 +9,10 @@ use app\models\connection\Dellin;
class DellinController extends Controller class DellinController extends Controller
{ {
public function actionCitiesImport() /**
* Импортировать города из ДеловыеЛинии
*/
public function actionImportCities()
{ {
if (Dellin::importCities()) { if (Dellin::importCities()) {
return ExitCode::OK; return ExitCode::OK;
@ -17,4 +20,16 @@ class DellinController extends Controller
return ExitCode::UNSPECIFIED_ERROR; return ExitCode::UNSPECIFIED_ERROR;
} }
/**
* Импортировать терминалы из ДеловыеЛинии
*/
public function actionImportTerminals()
{
if (Dellin::importTerminals()) {
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
} }

View File

@ -4,12 +4,14 @@ declare(strict_types=1);
namespace app\controllers; namespace app\controllers;
use app\models\Notification;
use yii; use yii;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\Order; use app\models\Order;
use app\models\OrderEdgeSupply;
use Exception; use Exception;
@ -24,7 +26,7 @@ class CartController extends Controller
{ {
// Инициализация // Инициализация
$page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1; $page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1;
$account = yii::$app->user; $account = yii::$app->user->identity;
// Поиск корзины (текущего заказа) // Поиск корзины (текущего заказа)
$model = Order::search(); $model = Order::search();
@ -41,7 +43,7 @@ class CartController extends Controller
} }
// Инициализация содержимого корзины // Инициализация содержимого корзины
$supplies = $model->content(10, $page); $connections = $model->content(10, $page);
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -49,13 +51,88 @@ class CartController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('index', compact('model', 'supplies')), 'main' => $this->renderPartial('index', compact('model', 'connections')),
'title' => 'Корзина', 'title' => 'Корзина',
'redirect' => '/cart', 'redirect' => '/cart',
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('index', compact('model', 'supplies')); return $this->render('index', compact('model', 'connections'));
}
public function actionEditComm(string $catn): array|string|null
{
// Инициализация
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
goto end;
}
if ($edges = OrderEdgeSupply::searchBySupplyCatn($catn)) {
// Рёбра найдены (связи заказа с поставкой)
// Инициализация
$amount = 0;
foreach ($edges as $edge) {
// Перебор рёбер (связей заказа с поставкой)
// Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? 'Комментарий к заказу';
$comm = $edge->comm ?? null;
$edge->comm = $text;
if ($edge->save()) {
// Ребро обновлено
// Запись в журнал
$edge->journal('update', ['comm' => ['from' => $comm, 'to' => $edge->comm]]);
// Обновление счётчика
++$amount;
// Запись в буфер ответа
$return['comm'] = $text;
}
}
if ($amount > 0) {
// Удалось записать минимум 1 связь с поставкой
Notification::_write("Обновлён комментарий к товару $catn ($amount шт)");
} else {
// Не удалось записать минимум 1 связь с поставкой
Notification::_write("Не удалось обновить комментарий к товару $catn");
}
}
/**
* Конец алгоритма
*/
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('/');
}
} }
} }

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|null { $search = function (bool $new = false, bool $count = false) use ($model, $account, $type, $let, $limit): array|int|null|Notification {
if ($count) { if ($count) {
// Запрошен подсчёт непрочитанных уведомлений // Запрошен подсчёт непрочитанных уведомлений
@ -164,7 +164,7 @@ class NotificationController extends Controller
goto end; goto end;
} }
foreach ($notifications as $notification) { foreach (is_array($notifications) ? $notifications : [$notifications] as $notification) {
// Перебор найденных уведомлений // Перебор найденных уведомлений
if ($preload) { if ($preload) {
@ -174,7 +174,7 @@ class NotificationController extends Controller
} }
// Запись ребра: ПОЛЬЗОВАТЕЛЬ -> УВЕДОМЛЕНИЕ (о том, что уведомление прочитано) // Запись ребра: ПОЛЬЗОВАТЕЛЬ -> УВЕДОМЛЕНИЕ (о том, что уведомление прочитано)
AccountEdgeNotification::write(yii::$app->user->id, $notification->readId(), $type); AccountEdgeNotification::write($account->readId(), $notification->readId(), $type);
} }
if (yii::$app->request->post('last')) { if (yii::$app->request->post('last')) {

View File

@ -96,7 +96,7 @@ class OrderController extends Controller
// POST-запрос // POST-запрос
// Инициализация входных данных // Инициализация входных данных
$account = yii::$app->user; $account = yii::$app->user->identity;
$supplies = yii::$app->request->post('supplies'); $supplies = yii::$app->request->post('supplies');
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
@ -122,7 +122,7 @@ class OrderController extends Controller
$model->save() or throw new Exception('Не удалось инициализировать заказ'); $model->save() or throw new Exception('Не удалось инициализировать заказ');
// Запись ребра: АККАУНТ -> ЗАКАЗ // Запись ребра: АККАУНТ -> ЗАКАЗ
AccountEdgeOrder::write($account->id, $model->readId(), 'current') or $model->addError('errors', 'Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ'); AccountEdgeOrder::write($account->readId(), $model->readId(), 'current') or $model->addError('errors', 'Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
} }
// Проверка входных данных // Проверка входных данных
@ -155,7 +155,7 @@ class OrderController extends Controller
// Инициализация // Инициализация
$targets = yii::$app->request->post('targets') ?? yii::$app->request->get('targets'); $targets = yii::$app->request->post('targets') ?? yii::$app->request->get('targets');
$page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1; $page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1;
$account = yii::$app->user; $account = yii::$app->user->identity;
$order = Order::search(); $order = Order::search();
if ($targets) { if ($targets) {
@ -182,7 +182,7 @@ class OrderController extends Controller
$order->journal('reserved'); $order->journal('reserved');
// Поиск // Поиск
$edge = AccountEdgeOrder::searchByVertex($account->id, $order->readId(), 'current'); $edge = AccountEdgeOrder::searchByVertex($account->readId(), $order->readId(), 'current');
if (count($edge) > 1) { if (count($edge) > 1) {
// Найден более чем 1 заказ // Найден более чем 1 заказ
@ -214,7 +214,7 @@ class OrderController extends Controller
} }
// Инициализация содержимого корзины // Инициализация содержимого корзины
$supplies = $order->content(10, $page); $connections = $order->content(10, $page);
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -222,14 +222,14 @@ class OrderController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('/cart/index', compact('order', 'supplies')), 'main' => $this->renderPartial('/cart/index', compact('order', 'connections')),
'title' => 'Корзина', 'title' => 'Корзина',
'redirect' => '/cart', 'redirect' => '/cart',
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('/cart/index', compact('order', 'supplies')); return $this->render('/cart/index', compact('order', 'connections'));
} }
/** /**
@ -242,7 +242,7 @@ class OrderController extends Controller
$page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1; $page = yii::$app->request->get('page') ?? yii::$app->request->post('page') ?? 1;
$account = yii::$app->user; $account = yii::$app->user;
$order = Order::search(); $order = Order::search();
$supplies = $order->content(10, $page); $connections = $order->content(10, $page);
foreach (isset($targets[0]) && is_array($targets[0]) ? $targets : [$targets] as $target) { foreach (isset($targets[0]) && is_array($targets[0]) ? $targets : [$targets] as $target) {
// Унификация входных параметров // Унификация входных параметров
@ -250,22 +250,22 @@ class OrderController extends Controller
foreach ($target as $catn => $amount) { foreach ($target as $catn => $amount) {
// Перебор целей (переданных объектов в корзине) // Перебор целей (переданных объектов в корзине)
foreach ($supplies as $supply) { foreach ($connections as $connection) {
// Перебор объектов в корзине // Перебор объектов в корзине
if ($supply->catn === $catn) { if ($connection['supply']['catn'] === $catn) {
// Цель найдена // Цель найдена
if ($supply->amnt > $amount) { if ($connection['amount'] > $amount) {
// Запрошено уменьшение количества // Запрошено уменьшение количества
// Удаление // Удаление
$order->deleteSupply([$catn => $supply->amnt - $amount]); $order->deleteSupply([$catn => $connection['amount'] - $amount]);
} else if ($supply->amnt < $amount) { } else if ($connection['amount'] < $amount) {
// Запрошено увеличение количества // Запрошено увеличение количества
// Запись // Запись
$order->writeSupply([$supply->catn => $amount - $supply->amnt]); $order->writeSupply([$connection['supply']['catn'] => $amount - $connection['amount']]);
} }
} }
} }
@ -273,7 +273,7 @@ class OrderController extends Controller
} }
// Ренициализация // Ренициализация
$supplies = $order->content(10, $page); $connections = $order->content(10, $page);
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -281,14 +281,14 @@ class OrderController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('/cart/index', compact('order', 'supplies')), 'main' => $this->renderPartial('/cart/index', compact('order', 'connections')),
'title' => 'Корзина', 'title' => 'Корзина',
'redirect' => '/cart', 'redirect' => '/cart',
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
return $this->render('/cart/index', compact('order', 'supplies')); return $this->render('/cart/index', compact('order', 'connections'));
} }
/** /**

View File

@ -208,7 +208,6 @@ class ProductController extends Controller
// Инициализация // Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0'; $text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0';
$text or $text = '0';
$dimension = yii::$app->request->post('dimension') ?? yii::$app->request->get('dimension') ?? 'x'; $dimension = yii::$app->request->post('dimension') ?? yii::$app->request->get('dimension') ?? 'x';
$product->dmns = array_merge( $product->dmns = array_merge(
@ -265,8 +264,6 @@ class ProductController extends Controller
// Инициализация // Инициализация
$text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0'; $text = yii::$app->request->post('text') ?? yii::$app->request->get('text') ?? '0';
$text or $text = '0';
$product->wght = $text; $product->wght = $text;
if ($product->save()) { if ($product->save()) {

View File

@ -12,6 +12,8 @@ use app\models\Product;
use app\models\Supply; use app\models\Supply;
use app\models\Search; use app\models\Search;
use app\models\connection\Dellin;
class SearchController extends Controller class SearchController extends Controller
{ {
/** /**
@ -167,9 +169,19 @@ class SearchController extends Controller
$row['overload'] = true; $row['overload'] = true;
} }
// Поиск аккаунтов владельцев поставок
foreach ($row['supplies'] as &$edge) { foreach ($row['supplies'] as &$edge) {
// Перебор поставок
// Инициализация аккаунта
$edge['account'] = Supply::searchAccountById($edge['_from']); $edge['account'] = Supply::searchAccountById($edge['_from']);
// Инициализация доставки
$edge['delivery'] = Dellin::calcDelivery($edge['account']['opts']['delivery_from_city'] ?? $settings['delivery_from_city_default'] ?? '2700000100000000000000000', yii::$app->user->identity->opts['delivery_to_city'] ?? $settings['delivery_to_city_default'] ?? '2700000100000000000000000')['terminals_standard'];
$edge['delivery']['max'] = $edge['delivery']['period_to'];
$edge['delivery']['price'] = $edge['delivery']['price'];
// Инициализация цены (цена поставки + цена доставки + наша наценка)
$edge['prce'] = ($edge['prce'] ?? $edge['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($edge['delivery']['price'] ?? 0) + ($settings['increase'] ?? 0);
} }
} }

View File

@ -36,6 +36,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface
[ [
'auth', 'auth',
'mail', 'mail',
'indx',
'pswd', 'pswd',
'name', 'name',
'simc', 'simc',
@ -60,6 +61,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface
[ [
'auth' => 'Аутентификационный хеш', 'auth' => 'Аутентификационный хеш',
'mail' => 'Почта', 'mail' => 'Почта',
'indx' => 'Псевдоанонимный идентификатор',
'pswd' => 'Пароль', 'pswd' => 'Пароль',
'name' => 'Имя', 'name' => 'Имя',
'simc' => 'Номер', 'simc' => 'Номер',
@ -82,9 +84,32 @@ class Account extends Document implements IdentityInterface, PartnerInterface
return array_merge( return array_merge(
parent::rules(), parent::rules(),
[ [
[['mail', 'pswd'], 'required', 'message' => 'Заполните поле'], [
['mail', 'email'], [
[['mail', 'comp', 'simc'], 'unique', 'message' => '2'] 'mail',
'pswd'
],
'required',
'message' => 'Заполните поле'
],
[
'mail',
'email'
],
[
[
'mail',
'indx',
'comp',
'simc'
],
'unique',
'message' => 'Атрибут {attribute} должен иметь уникальное значение'
],
[
'indx',
'string'
]
] ]
); );
} }
@ -356,7 +381,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
* *
* @return array Сортированный список * @return array Сортированный список
*/ */
protected function syncListWithSettings(array &$list, string $var): array { protected function syncListWithSettings(array &$list, string $var): array
{
// Инициализация текущего значения параметра в начале массива // Инициализация текущего значения параметра в начале массива
if (isset($this->opts[$var])) { if (isset($this->opts[$var])) {
// Параметр найден в настройках аккаунта // Параметр найден в настройках аккаунта
@ -387,4 +413,65 @@ class Account extends Document implements IdentityInterface, PartnerInterface
return $list; return $list;
} }
/**
* Генерация псевдоанонимных индексов
*
* @param [int] $accounts Массив аккаунтов
* @param bool $init Параметр обозначающий изменение только для тех у кого ранее идентификатор задан не был (без перезаписи)
*
* @return int Количество успешно обработанных аккаунтов
*/
public static function generateIndexes(array $accounts, bool $init = true): int
{
// Инициализация
$amount = 0;
// Функция для конвертации цифр в буквы
$int_to_string = function (int $target): string {
$alphabet = ['А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я'];
return $alphabet[$target];
};
foreach ($accounts as $account) {
// Перебор запрошенных аккаунтов
if (($init && empty($account->indx)) || !$init) {
// Запись только тех аккаунтов у кого не инициализирован индекс или если указано отсутствие проверки
// Повтор генерации
regenerate:
// Генерация
$account->indx = $int_to_string(random_int(0, 32)) . $int_to_string(random_int(0, 32)) . $int_to_string(random_int(0, 32));
// Запись
if ($account->save()) {
// Аккаунт сохранён (в базе данных это поле проверяется на уникальность)
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo "Удалось сохранить аккаунт с псевдоанонимным идентификатором $account->indx" . PHP_EOL;
}
// Запись операции в счётчик
$amount++;
} else {
// Аккаунт не сохранен (подразумевается несовпадение идентификаторов)
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo "Не удалось сохранить аккаунт с псевдоанонимным идентификатором $account->indx" . PHP_EOL;
}
goto regenerate;
}
}
}
return $amount;
}
} }

View File

@ -11,6 +11,8 @@ use app\models\traits\SearchByEdge;
use carono\exchange1c\controllers\ApiController; use carono\exchange1c\controllers\ApiController;
use carono\exchange1c\interfaces\DocumentInterface; use carono\exchange1c\interfaces\DocumentInterface;
use app\models\connection\Dellin;
use Exception; use Exception;
/** /**
@ -45,6 +47,7 @@ class Order extends Document implements DocumentInterface
parent::attributes(), parent::attributes(),
[ [
'ocid', 'ocid',
'stts',
'sync' 'sync'
] ]
); );
@ -59,6 +62,7 @@ class Order extends Document implements DocumentInterface
parent::attributeLabels(), parent::attributeLabels(),
[ [
'ocid' => 'Идентификатор 1C', 'ocid' => 'Идентификатор 1C',
'stts' => 'Статус',
'sync' => 'Статус синхронизации с 1C' 'sync' => 'Статус синхронизации с 1C'
] ]
); );
@ -92,7 +96,7 @@ class Order extends Document implements DocumentInterface
public function connect(Account $account): ?AccountEdgeOrder public function connect(Account $account): ?AccountEdgeOrder
{ {
// Запись ребра: АККАУНТ -> ЗАКАЗ // Запись ребра: АККАУНТ -> ЗАКАЗ
return AccountEdgeOrder::write($account->id, $this->readId(), 'current') ?? throw new Exception('Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ'); return AccountEdgeOrder::write($account->readId(), $this->readId(), 'current') ?? throw new Exception('Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ');
} }
/** /**
@ -110,7 +114,7 @@ class Order extends Document implements DocumentInterface
public function writeSupply(Supply|string|array $supply, Account $trgt = null): int public function writeSupply(Supply|string|array $supply, Account $trgt = null): int
{ {
// Инициализация // Инициализация
$trgt ?? $trgt = yii::$app->user ?? throw new Exception('Не удалось инициализировать заказчика'); $trgt ?? $trgt = yii::$app->user->identity ?? throw new Exception('Не удалось инициализировать заказчика');
if ($supply instanceof Supply) { if ($supply instanceof Supply) {
// Передана инстанция класса поставки или второй элемент массива не является числом // Передана инстанция класса поставки или второй элемент массива не является числом
@ -235,7 +239,7 @@ class Order extends Document implements DocumentInterface
public static function search(Account $account = null, string $type = 'current', int $limit = 1, int $page = 1, string $select = null): self|array|null public static function search(Account $account = null, string $type = 'current', int $limit = 1, int $page = 1, string $select = null): self|array|null
{ {
// Инициализация // Инициализация
$account or $account = yii::$app->user ?? throw new Exception('Не удалось инициализировать пользователя'); $account or $account = yii::$app->user->identity ?? throw new Exception('Не удалось инициализировать пользователя');
// Генерация сдвига по запрашиваемым данным (пагинация) // Генерация сдвига по запрашиваемым данным (пагинация)
$offset = $limit * ($page - 1); $offset = $limit * ($page - 1);
@ -255,7 +259,7 @@ class Order extends Document implements DocumentInterface
to: 'order', to: 'order',
subquery_where: [ subquery_where: [
[ [
'account._id' => $account->id 'account._id' => $account->readId()
], ],
$where_type $where_type
], ],
@ -281,8 +285,8 @@ class Order extends Document implements DocumentInterface
// Генерация сдвига по запрашиваемым данным (пагинация) // Генерация сдвига по запрашиваемым данным (пагинация)
$offset = $limit * ($page - 1); $offset = $limit * ($page - 1);
// Поиск рёбер: ЗАКАЗ -> ПОСТАВКА // Поиск по рёбрам: ЗАКАЗ -> ПОСТАВКА
$supplies = Supply::searchByEdge( $connections = Supply::searchByEdge(
from: 'order', from: 'order',
to: 'supply', to: 'supply',
edge: 'order_edge_supply', edge: 'order_edge_supply',
@ -295,21 +299,22 @@ class Order extends Document implements DocumentInterface
where: 'edge._to == supply._id', where: 'edge._to == supply._id',
limit: $limit, limit: $limit,
offset: $offset, offset: $offset,
direction: 'INBOUND' direction: 'INBOUND',
select: '{supply, order_edge_supply}'
); );
// Инициализация реестра дубликатов // Инициализация реестра дубликатов
$registry = []; $registry = [];
// Подсчёт и перестройка массива для очистки от дубликатов // Подсчёт и перестройка массива для очистки от дубликатов
foreach ($supplies as $key => &$supply) { foreach ($connections as $key => &$connection) {
// Перебор поставок // Перебор поставок
if (in_array($supply->catn, $registry)) { if (in_array($connection['supply']['catn'], $registry)) {
// Если данная поставка найдена в реестре // Если данная поставка найдена в реестре
// Удаление // Удаление
unset($supplies[$key]); unset($connections[$key]);
// Пропуск итерации // Пропуск итерации
continue; continue;
@ -319,47 +324,56 @@ class Order extends Document implements DocumentInterface
$amount = 0; $amount = 0;
// Повторный перебор для поиска дубликатов // Повторный перебор для поиска дубликатов
foreach ($supplies as &$supply4check) { foreach ($connections as &$connection_for_check) {
if ($supply == $supply4check) { if ($connection == $connection_for_check) {
// Найден дубликат // Найден дубликат
// Постинкрементация счётчика // Постинкрементация счётчика
$amount++; $amount++;
// Запись в реестр // Запись в реестр
$registry[] = $supply4check->catn; $registry[] = $connection_for_check['supply']['catn'];
} }
} }
// Запись количества для заказа // Запись количества для заказа
$supply->amnt = $amount; $connection['amount'] = $amount;
} }
// Поиск стоимости для каждой поставки // Инициализация дополнительных данных
foreach ($supplies as $key => &$supply) { foreach ($connections as $key => &$connection) {
// Перебор поставок // Перебор поставок
// Чтение стоимости // Чтение стоимости
$cost = $supply->readCost(); $cost = Supply::readCostById($connection['supply']['_id']);
if ($cost < 1) { if (empty($cost) || $cost['ЦенаЗаЕдиницу'] < 1) {
// Если стоимость равна нулю (явная ошибка) // Если стоимость не найдена или равна нулю (явная ошибка)
// Удаление из базы данных // Удаление из базы данных
$this->deleteSupply($supply->readId()); $this->deleteSupply($connection['supply']->readId());
// Удаление из списка // Удаление из списка
unset($supplies[$key]); unset($connections[$key]);
// Пропуск итерации // Пропуск итерации
continue; continue;
} }
// Запись цены // Поиск ребра до аккаунта
$supply->cost = $cost['ЦенаЗаЕдиницу'] . ' ' . $cost['Валюта']; $connection['account'] = Supply::searchAccountById($connection['supply']['_id']);
// Инициализация доставки
$connection['delivery'] = Dellin::calcDelivery($connection['account']['opts']['delivery_from_city'] ?? $settings['delivery_from_city_default'] ?? '2700000100000000000000000', yii::$app->user->identity->opts['delivery_to_city'] ?? $settings['delivery_to_city_default'] ?? '2700000100000000000000000')['terminals_standard'];
// Запись цены (цена поставки + цена доставки + наша наценка)
$connection['cost'] = ($cost['ЦенаЗаЕдиницу'] ?? $connection['supply']->onec['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($connection['delivery']['price'] ?? 0) + ($settings['increase'] ?? 0);
// Запись валюты
$connection['currency'] = $cost['Валюта'];
} }
return $supplies; return $connections;
} }
/** /**

View File

@ -6,8 +6,57 @@ namespace app\models;
class OrderEdgeSupply extends Edge class OrderEdgeSupply extends Edge
{ {
/**
* Имя коллекции
*/
public static function collectionName(): string public static function collectionName(): string
{ {
return 'order_edge_supply'; return 'order_edge_supply';
} }
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'comm'
]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'comm' => 'Комментарий к заказу'
]
);
}
/**
* Поиск поставки по артикулу
*
* @param string $catn Артикул
* @param int $limit Максимальное количество
*
* @return array Поставки
*/
public static function searchBySupplyCatn(string $catn, int $limit = 10): array
{
if ($supply = Supply::searchByCatn($catn, 1)) {
// Поставка найдена
return self::find()->where(['_to' => $supply->readId()])->limit($limit)->all();
}
return [];
}
} }

View File

@ -565,14 +565,31 @@ class Supply extends Product implements ProductInterface, OfferInterface
/** /**
* Прочитать стоимость * Прочитать стоимость
*
* @param Product|null $product Товар для поиска по вершинам
*
* @return array|null Данные о ценах
*/ */
public function readCost(Product $product = null): array public function readCost(Product $product = null): ?array
{ {
if (isset($product)) { return static::readCostById($this->readId(), $product);
return SupplyEdgeProduct::searchByVertex($this->readId(), $product->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена'];
} }
return SupplyEdgeProduct::search($this->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена']; /**
* Прочитать стоимость по идентификатору поставки
*
* @param string $_id Идентификатор поставки
* @param Product|null $product Товар для поиска по вершинам
*
* @return array|null Данные о ценах
*/
public static function readCostById(string $_id, Product $product = null): ?array
{
if (isset($product)) {
return SupplyEdgeProduct::searchByVertex($_id, $product->readId(), type: 'connect', limit: 1)['onec']['Цены']['Цена'];
}
return SupplyEdgeProduct::search($_id, type: 'connect', limit: 1)['onec']['Цены']['Цена'];
} }
/** /**
@ -582,7 +599,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
*/ */
public function searchAccount(): ?array public function searchAccount(): ?array
{ {
return static::searchAccountById($this->_id); return static::searchAccountById($this->readId());
} }
/** /**

View File

@ -55,6 +55,76 @@ class Dellin extends Model
// }); // });
// } // }
/**
* Рассчет доставки (расширенный)
*
* @param string $from Номер КЛАДР
* @param string $to Номер КЛАДР
* @param int $weight Вес (кг)
* @param int $x Ширина (см)
* @param int $y Высота (см)
* @param int $z Длинна (см)
*
* @return string
*/
public static function calcDeliveryAdvanced(string $from, string $to, int $weight, int $x, int $y, int $z): array
{
return self::handle(function () use ($from, $to, $weight, $x, $y, $z) {
// Всё готово к работе
// Рассчёт типа доставки
if (
$weight <= 10
&& (($x <= 5.4 && $y <= 3.9 && $z <= 3.9)
|| ($x <= 3.9 && $y <= 5.4 && $z <= 3.9)
|| ($x <= 3.9 && $y <= 3.9 && $z <= 5.4))
&& $x * $y * $z <= 1000000
) {
// Доставка категории "small"
$type = 'small';
} else {
// Доставка категории "auto"
$type = 'auto';
}
// Запрос
$request = self::$browser->post('/v1/micro_calc.json', [
'json' => [
'appkey' => yii::$app->params['dellin']['key'],
'sessionID' => self::$session,
'delivery' => [
'deliveryType' => [
'type' => $type
],
'arrival' => [
'variant' => 'terminal',
]
]
]
]);
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);
});
}
/** /**
* Рассчет доставки * Рассчет доставки
* *
@ -103,9 +173,9 @@ class Dellin extends Model
} }
/** /**
* Синхронизация городов * Импорт городов
* *
* @return array|null Найденные города * @return array|null Сохранённые города
*/ */
public static function importCities(): ?int public static function importCities(): ?int
{ {
@ -137,8 +207,8 @@ class Dellin extends Model
'sink' => $file = $dir . time() . '.csv' 'sink' => $file = $dir . time() . '.csv'
]); ]);
// Проверка хеша // Проверка хеша (оказалось это хеш запроса, бесполезный)
if ($hash_target !== $hash_received = md5_file($file)) { // if ($hash_target === $hash_received = md5_file($file)) {
// Удалось пройти проверку на хеши файлов // Удалось пройти проверку на хеши файлов
// Инициализация (чтение файла) // Инициализация (чтение файла)
@ -220,11 +290,134 @@ class Dellin extends Model
fclose($file); fclose($file);
return $amount; return $amount;
} else { // } else {
// Не удалось пройти проверку на соответствие хешей файлов // // Не удалось пройти проверку на соответствие хешей файлов
throw new Exception('Хеши файлов не совпадают. Должен быть: "' . $hash_target . '", получен: "' . $hash_received . '"', 500); // throw new Exception('Хеши файлов не совпадают. Должен быть: "' . $hash_target . '", получен: "' . $hash_received . '"', 500);
// }
} }
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500);
});
}
/**
* Импорт терминалов
*
* @return array|null Сохранённые терминалы
*/
public static function importTerminals(): ?int
{
return self::handle(function () {
// Всё готово к работе
// Запрос ссылки на файл с городами, возвращает ['hash' => string, 'url' => string]
$request = self::$browser->post('/v3/public/terminals.json', [
'json' => [
'appkey' => yii::$app->params['dellin']['key'],
]
]);
if ($request->getStatusCode() === 200) {
// Запрос прошел успешно
// Инициализация
$response = json_decode((string) $request->getBody(), true);
$dir = YII_PATH_PUBLIC . '/../assets/import/' . date('Y-m-d', time()) . '/dellin/terminals/' . (yii::$app->user->identity->_key ?? 'system') . '/';
if (!file_exists($dir)) {
// Директории не существует
mkdir($dir, 0775, true);
}
$request = self::$browser->get($response['url'], [
'sink' => $file = $dir . time() . '.json'
]);
die;
// Инициализация (чтение файла)
$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;
// Очистка
array_walk($data, fn (&$value) => $value = trim($value, '"'));
if ($city = Terminal::searchByDellinId($data[0])) {
// Удалось найти город в базе данных
$after_import_log = function () use ($city): void {
// Запись в журнал
$city->journal('update');
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось перезаписать город: ' . $city->name . PHP_EOL;
}
};
} else {
// Не удалось найти город в базе данных
$terminal = new Terminal();
$after_import_log = function () use ($terminal): void {
if (yii::$app->getRequest()->isConsoleRequest) {
// Вызов из терминала
echo 'Удалось записать город: ' . $terminal->name . PHP_EOL;
}
};
}
// Запись
$terminal->indx = $data[0];
$terminal->name = $data[1];
$terminal->code = $data[2];
$terminal->term = (bool) $data[3];
// Отправка в базу данных
if ($terminal->save()) {
// Удалось сохранить в базе данных
// Запись в журнал
$after_import_log();
// Постинкрементация счётчика
$amount++;
continue;
} else {
// Не удалось сохранить в базе данных
throw new Exception('Не удалось сохранить город ' . $data[1] . ' в базу данных', 500);
}
}
// Деинициализация
fclose($file);
return $amount;
} }
throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500); throw new Exception('Не удалось синхронизировать данные городов с ДеловыеЛинии', 500);

View File

@ -69,6 +69,7 @@ trait SearchByEdge
$request = $query $request = $query
->foreach($foreach) ->foreach($foreach)
->where($where) ->where($where)
->limit($limit)
->select($select ?? $to); ->select($select ?? $to);
// Режим проверки // Режим проверки
@ -84,6 +85,8 @@ trait SearchByEdge
return $handle($request); return $handle($request);
} else if (isset($select)) { } else if (isset($select)) {
// Указан выбор свойств
$response = $request->createCommand()->execute()->getAll(); $response = $request->createCommand()->execute()->getAll();
if ($asArray) { if ($asArray) {
@ -103,9 +106,7 @@ trait SearchByEdge
return $response; return $response;
} else { } else {
if ($limit === 1) { // Иначе просто запросить для ActiveQuery
return $request->one();
}
return $request->all(); return $request->all();
} }

View File

@ -1,3 +1,11 @@
<?php
use yii;
use app\models\connection\Dellin;
?>
<link href="/css/pages/cart.css" rel="stylesheet"> <link href="/css/pages/cart.css" rel="stylesheet">
<div id="page_cart" class="container mb-auto py-3"> <div id="page_cart" class="container mb-auto py-3">
@ -6,7 +14,7 @@
<div class="col mb-4 list rounded overflow-hidden"> <div class="col mb-4 list rounded overflow-hidden">
<div class="row py-2"> <div class="row py-2">
<div class="pl-3 mr-1"> <div class="pl-3 mr-1">
<input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);"/> <input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);" />
</div> </div>
<div class="col-2"> <div class="col-2">
<span>Артикул</span> <span>Артикул</span>
@ -25,27 +33,48 @@
</div> </div>
</div> </div>
<?php <?php
if (isset($supplies) && !empty($supplies)) { if (isset($connections) && !empty($connections)) {
foreach ($supplies as $supply) { foreach ($connections as $connection) {
// Перебор поставок
// Инициализация доставки
$delivery = $connection['delivery']['period_to'];
// Инициализация цены
$cost = $connection['cost'] . ' ' . $connection['currency'];
// Инициализация комментария
$comment = $connection['order_edge_supply'][0]['comm'] ?? 'Комментарий к заказу';
echo <<<HTML echo <<<HTML
<div class="row py-2 cart_list_target"> <div class="row py-2 cart_list_target">
<div class="pl-3 mr-1"> <div class="col">
<input id="cart_list_checkbox_$supply->catn" type="checkbox" onchange="return cart_list_checkbox(this);"/> <div class="row">
<div class="pl-3 my-auto mr-1">
<input id="cart_list_checkbox_{$connection['supply']['catn']}" type="checkbox" onchange="return cart_list_checkbox(this);"/>
</div> </div>
<div class="col-2"> <div class="col-2 my-auto">
$supply->catn {$connection['supply']['catn']}
</div> </div>
<div class="col-4"> <div class="col-4 my-auto">
$supply->dscr {$connection['supply']['dscr']}
</div> </div>
<div class="col-1 ml-auto"> <div class="col-1 my-auto ml-auto">
<input id="cart_list_amnt_$supply->catn" class="form-control text-center" type="text" value="$supply->amnt" onchange="return cart_list_amount_update('$supply->catn', this)" aria-invalid="false"> <input id="cart_list_amnt_{$connection['supply']['catn']}" class="form-control text-center" type="text" value="{$connection['amount']}" onchange="return cart_list_amount_update('{$connection['supply']['catn']}', this)" aria-invalid="false">
</div>
<div class="col-2 my-auto text-right">
$delivery дней
</div>
<div class="col-2 my-auto mr-3 text-right">
$cost
</div>
</div>
<div class="dropdown-divider"></div>
<div class="row mb-1">
<div class="col-12">
<p id="cart_list_comment_{$connection['supply']['catn']}" class="mt-0 ml-0 text-break pointer-event" role="button" onclick="return cart_list_comment_edit('{$connection['supply']['catn']}', this);">$comment</p>
</div> </div>
<div class="col-2 text-right">
$supply->time
</div> </div>
<div class="col-2 mr-3 text-right">
$supply->cost
</div> </div>
</div> </div>
HTML; HTML;

View File

@ -1,11 +1,3 @@
<?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">
@ -75,7 +67,7 @@ use app\models\connection\Dellin;
$catn = $supply['catn'] ?? $supply['onec']['Артикул']; $catn = $supply['catn'] ?? $supply['onec']['Артикул'];
// Инициализация цены // Инициализация цены
$price_raw = $supply['prce'] ?? $supply['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']; $price_raw = $supply['prce'];
$price = $price_raw . ' ' . $supply['onec']['Цены']['Цена']['Валюта']; $price = $price_raw . ' ' . $supply['onec']['Цены']['Цена']['Валюта'];
// Инициализация количества // Инициализация количества
@ -88,9 +80,10 @@ use app\models\connection\Dellin;
} }
// Инициализация доставки // Инициализация доставки
$delivery = Dellin::calcDelivery($supply['account']['opts']['delivery_from_city'], yii::$app->user->identity->opts['delivery_to_city'])['terminals_standard']; $delivery = $supply['delivery']['max'];
$delivery_max = $delivery['period_to'] ?? 'Неизвестно';
$delivery_price = $delivery['price'] ?? 'Неизвестно'; // Инициализация индекса аккаунта
$index = $supply['account']['indx'] ?? 'Неизвестен';
if ($amount_raw < 1 || $price_raw < 1) { if ($amount_raw < 1 || $price_raw < 1) {
// Нет в наличии или цена 0 рублей // Нет в наличии или цена 0 рублей
@ -109,17 +102,19 @@ use app\models\connection\Dellin;
$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">
<small class="col-2 my-auto ml-auto"> <small class="col-1 my-auto ml-auto pl-2 pr-0">
$index
</small>
<small class="col-1 my-auto pl-2 pr-0">
$amount $amount
</small> </small>
<small class="col-2 my-auto ml-1"> <small class="col-1 my-auto pl-2 pr-0 mr-2">
$delivery_max дней<br/> $delivery дн
$delivery_price рублей
</small> </small>
<b class="col-2 my-auto"> <b class="col-2 my-auto">
$price $price
</b> </b>
<a class="col-1 h-100 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" href="/cart" role="button" onclick="return cart_write('$catn');"> <a class="col-1 h-100 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" role="button" onclick="return cart_write('$catn');">
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i> <i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
</a> </a>
</div> </div>

View File

@ -2,11 +2,12 @@
background-color: #fff; background-color: #fff;
} }
#page_cart article .list .row:nth-child(2n+1) { #page_cart article .list > .row:nth-child(2n+1) {
background-color: #f7f6f9; background-color: #f7f6f9;
} }
#page_cart article .list .row:first-child { #page_cart article .list > .row:first-child {
/* border-bottom: 2px solid #bcc2d5; */
background-color: #dbdde3; background-color: #dbdde3;
} }

View File

@ -188,6 +188,61 @@ function cart_cost_calculate() {
document.getElementById('cart_cost').innerHTML = costs; document.getElementById('cart_cost').innerHTML = costs;
} }
/**
* Изменить количество товара в корзине
*/
function cart_list_comment_edit(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
element.innerHTML = '<textarea class="form-control" cols="50" rows="5" onchange = "return cart_list_comment_save(\'' + catn + '\', this.parentElement)">' + element.innerText + '</textarea>';
element.removeAttribute('onclick');
return false;
}
return true;
}
function cart_list_comment_save(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
// Инициализация
let text = element.children[0].value;
// Обновление заголовка (предзагрузка)
element.innerHTML = text;
// Запись аттрибута (предзагрузка)
element.setAttribute('onclick', 'return cart_list_comment_edit(\'' + catn + '\', this);');
$.ajax({
url: '/cart/' + catn + '/edit/comm',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'text': text
},
success: function (data, status) {
// Заголовок
if (data.comm !== undefined && element !== null && element !== undefined) {
// Обновление заголовка
element.innerHTML = data.comm;
// Запись аттрибута
element.setAttribute('onclick', 'return cart_list_comment_edit(\'' + catn + '\', this);');
};
cart_response_success(data, status);
},
error: cart_response_error
});
return false;
}
return true;
}
cart_cost_calculate(); cart_cost_calculate();
function cart_response(data, status) { function cart_response(data, status) {