This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-12-17 00:44:04 +10:00
commit 1918db868d
105 changed files with 3874 additions and 5207 deletions

View File

@ -13,7 +13,7 @@
} }
], ],
"require": { "require": {
"php": "^8.0.0", "php": "^8.1.0",
"ext-intl": "~8.0", "ext-intl": "~8.0",
"twbs/bootstrap": "4.6.0", "twbs/bootstrap": "4.6.0",
"yiisoft/yii2": "2.*", "yiisoft/yii2": "2.*",

4829
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -24,9 +24,9 @@ class DellinController extends Controller
/** /**
* Импортировать терминалы из ДеловыеЛинии * Импортировать терминалы из ДеловыеЛинии
*/ */
public function actionImportTerminals() public function actionImportTerminals(?int $account = null)
{ {
if (Dellin::importTerminals()) { if (Dellin::importTerminals($account)) {
return ExitCode::OK; return ExitCode::OK;
} }

View File

@ -0,0 +1,63 @@
<?php
namespace app\commands;
use yii\console\Controller;
use yii\console\ExitCode;
use moonland\phpexcel\Excel;
use app\models\Supply;
use app\models\File;
class ImportController extends Controller
{
/**
* Импортировать города из ДеловыеЛинии
*/
public function actionCities()
{
if (Dellin::importCities()) {
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
/**
* Импортировать терминалы из ДеловыеЛинии
*/
public function actionTerminals()
{
if (Dellin::importTerminals()) {
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
/**
* Импортировать поставки из файлов
*/
public function actionSupplies(int $amount = 3)
{
try {
$files = File::searchSuppliesNeededToLoad($amount);
if ($files > 0) {
// Найдены файлы
foreach ($files as $file) {
// Перебор файлов для загрузки
// Загрузка в базу данных
Supply::loadExcel($file);
}
}
} catch (exception $e) {
return ExitCode::UNSPECIFIED_ERROR;
}
return ExitCode::OK;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace app\commands;
use yii\console\Controller;
use yii\console\ExitCode;
use app\models\Supply;
use app\models\File;
class SuppliesController extends Controller
{
/**
* Импортировать города из ДеловыеЛинии
*/
public function actionImport(int $amount = 3)
{
if (Dellin::importCities($amount)) {
return ExitCode::OK;
}
return ExitCode::UNSPECIFIED_ERROR;
}
}

View File

@ -11,6 +11,7 @@ use yii\web\Cookie;
use yii\filters\AccessControl; use yii\filters\AccessControl;
use app\models\Account; use app\models\Account;
use app\models\AccountForm;
class AccountController extends Controller class AccountController extends Controller
{ {
@ -24,7 +25,9 @@ class AccountController extends Controller
'allow' => true, 'allow' => true,
'actions' => [ 'actions' => [
'file', 'file',
'data' 'data',
'restore',
'generate-password'
] ]
], ],
[ [
@ -38,7 +41,10 @@ class AccountController extends Controller
], ],
[ [
'allow' => true, 'allow' => true,
'actions' => ['accept', 'decline'], 'actions' => [
'read',
'accept',
'decline'],
'matchCallback' => function ($rule, $action): bool { 'matchCallback' => function ($rule, $action): bool {
if ( if (
!yii::$app->user->isGuest !yii::$app->user->isGuest
@ -352,4 +358,127 @@ class AccountController extends Controller
]; ];
} }
} }
/**
* Восстановление пароля
*
* @return string
*/
public function actionRestore()
{
// Инициализация
$model = new AccountForm(yii::$app->request->post('AccountForm'));
$type = yii::$app->request->post('type') ?? yii::$app->request->get('type');
$target = yii::$app->request->post('target') ?? yii::$app->request->get('target');
// Фильтрация
$target = match ($target) {
'panel' => 'panel',
'main' => 'main',
default => 'main'
};
// Рендер для всплывающей панели
$panel = $target === 'panel';
if (yii::$app->request->isPost) {
// AJAX-POST-запрос
// Настройка кода ответа
yii::$app->response->format = Response::FORMAT_JSON;
if (yii::$app->user->isGuest || $model->validate()) {
// Аккаунт не аутентифицирован и проверка пройдена
// Отправка запроса на генерацию пароля и запись ответа
$return = [
'status' => Account::restoreSend($model->mail),
'_csrf' => yii::$app->request->getCsrfToken()
];
return $return;
} else {
// Аккаунт аутентифицирован
// Настройка кода ответа
yii::$app->response->statusCode = 400;
return [
'redirect' => '/',
'_csrf' => yii::$app->request->getCsrfToken()
];
}
}
}
/**
* Генерация нового пароля
*
* @return string
*/
public function actionGeneratePassword(string $id, string $key)
{
if ($account = Account::searchById(Account::collectionName() . "/$id")) {
// Найден аккаунт
if ($account->chpk === $key) {
// Ключи совпадают
// Инициализация буфера пароля
$old = $account->pswd;
// Генерация пароля
$account->restoreGenerate();
if ($account->pswd !== $old) {
// Успешно сгенерирован новый пароль
// Инициализация формы аутентификации
$form = new AccountForm;
// Запись параметров
$form->mail = $account->mail;
$form->pswd = $account->pswd;
// Аутентификация
$form->authentication();
}
}
}
// Перенаправление на главную страницу
$this->redirect('/');
}
/**
* Генерация нового пароля
*
* @return string
*/
public function actionRead(int $page = 1): string|array|null
{
if (yii::$app->request->isPost) {
// POST-запрос
// Инициализация входных параметров
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 20;
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
// Инициализация cookie
$cookies = yii::$app->response->cookies;
// Чтение аккаунтов
$accounts = Account::read(limit: $amount, page: $page, order: $order);
// Запись формата ответа
yii::$app->response->format = Response::FORMAT_JSON;
return [
'accounts' => $this->renderPartial('/account/list', compact('accounts', 'amount', 'page')),
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return false;
}
} }

View File

@ -4,6 +4,7 @@ namespace app\controllers;
use app\models\AccountForm; use app\models\AccountForm;
use app\models\Order; use app\models\Order;
use app\models\Notification;
use yii; use yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
@ -61,6 +62,9 @@ class AuthenticationController extends Controller
if (!yii::$app->user->isGuest || $model->authentication()) { if (!yii::$app->user->isGuest || $model->authentication()) {
// Аккаунт аутентифицирован // Аккаунт аутентифицирован
// Отправка уведомления
Notification::_write('Вы аутентифицированы с устройства ' . $_SERVER['HTTP_USER_AGENT'] . ' ' . $_SERVER['REMOTE_ADDR'], true, yii::$app->user->identity->_key, Notification::TYPE_NOTICE);
// Инициализация // Инициализация
$notifications_button = $this->renderPartial('/notification/button'); $notifications_button = $this->renderPartial('/notification/button');
$notifications_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]); $notifications_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]);

View File

@ -88,7 +88,6 @@ class CartController extends Controller
// Поиск корзины (текущего заказа) // Поиск корзины (текущего заказа)
$data = Order::searchSmart()[0] ?? null; $data = Order::searchSmart()[0] ?? null;
if (empty($data['order'])) { if (empty($data['order'])) {
// Корзина не инициализирована // Корзина не инициализирована

View File

@ -18,10 +18,14 @@ class OfferController extends Controller
'access' => [ 'access' => [
'class' => AccessControl::class, 'class' => AccessControl::class,
'rules' => [ 'rules' => [
[
'allow' => true,
'actions' => ['index', 'suppliers'],
],
[ [
'allow' => true, 'allow' => true,
'roles' => ['@'], 'roles' => ['@'],
'actions' => ['index', 'suppliers', 'accept', 'accept-suppliers'] 'actions' => ['accept', 'accept-suppliers']
], ],
[ [
'allow' => false, 'allow' => false,

View File

@ -56,6 +56,21 @@ class OrderController extends Controller
'supply-edit-comm' 'supply-edit-comm'
] ]
], ],
[
'allow' => true,
'actions' => [
'list'
],
'matchCallback' => function ($rule, $action): bool {
if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) return true;
return false;
}
],
[ [
'allow' => false, 'allow' => false,
'roles' => ['?'], 'roles' => ['?'],
@ -283,37 +298,11 @@ class OrderController extends Controller
to: $to, to: $to,
search: $search search: $search
); );
// Инициализация панели модератора заказов
if (!yii::$app->user->isGuest && Account::isMinimalAuthorized($account)) {
// Имеет доступ пользователь
// Инициализация заказов для модератора
$moderator_data = Order::searchSmart(account: '@all', stts: '@all', limit: 10, page: 1, supplies: true);
} else {
// Не имеет доступ пользователь
// Инициализация заглушки
$moderator_data = null;
}
} else if ($window === 'orders_panel_moderation') { } else if ($window === 'orders_panel_moderation') {
// Обработка панели модерации заказов // Обработка панели модерации заказов
// Инициализация заказов // Инициализация заказов
$data = Order::searchSmart( $data = Order::searchSmart(
stts: '@all',
limit: 10,
page: 1,
supplies: true
);
// Инициализация панели модератора заказов
if (!yii::$app->user->isGuest && Account::isMinimalAuthorized($account)) {
// Имеет доступ пользователь
// Инициализация заказов для модератора
$moderator_data = Order::searchSmart(
account: '@all',
stts: $stts, stts: $stts,
limit: 10, limit: 10,
page: 1, page: 1,
@ -322,12 +311,7 @@ class OrderController extends Controller
to: $to, to: $to,
search: $search search: $search
); );
} else {
// Не имеет доступ пользователь
// Инициализация заглушки
$moderator_data = null;
}
} else { } else {
// Запрошено неизвестное окно // Запрошено неизвестное окно
} }
@ -343,7 +327,7 @@ class OrderController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('/orders/index', compact('data', 'moderator_data', 'account', 'search', 'from', 'to', 'window') 'main' => $this->renderPartial('/orders/index', compact('data', 'account', 'search', 'from', 'to', 'window')
+ ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['data' => $data] ?? null)]), + ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['data' => $data] ?? null)]),
'title' => 'Заказы', 'title' => 'Заказы',
'redirect' => '/orders', 'redirect' => '/orders',
@ -351,7 +335,7 @@ class OrderController extends Controller
]; ];
} }
return $this->render('/orders/index', compact('data', 'moderator_data', 'account')); return $this->render('/orders/index', compact('data', 'account'));
} }
/** /**
@ -822,6 +806,60 @@ class OrderController extends Controller
} }
} }
/**
* Список заказов для модерации
*
* @param string $catn Артикул
*/
public function actionList(int $page): array|string|null
{
if (!yii::$app->user->isGuest && Account::isMinimalAuthorized()) {
// Авторизован пользователь
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
// Инициализация количества
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 8;
if ($moderator_data = Order::searchSmart(
account: '@all',
stts: '@all',
limit: (int) $amount,
page: $page,
supplies: true
)) {
// Найдены заказы
// Генерация списка
$list = trim($this->renderPartial('/orders/moderation', compact('moderator_data')));
if (!empty($list)) $return['list'] = $list;
} else {
// Не найдены заказы
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
// $return['alert'] = "Не удалось найти заказы для генерации списка";
}
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
}
// Переадресация на главную страницу
return $this->redirect("/");
}
/** /**
* Чтение инстанции поставки в заказе (order_edge_supply) * Чтение инстанции поставки в заказе (order_edge_supply)
* *

View File

@ -33,6 +33,7 @@ class ProductController extends Controller
'allow' => true, 'allow' => true,
'actions' => [ 'actions' => [
'index', 'index',
'analogs'
] ]
], ],
[ [
@ -81,6 +82,36 @@ class ProductController extends Controller
]; ];
} }
public function accessDenied()
{
// Инициализация
$cookies = yii::$app->response->cookies;
// Запись cookie с редиректом, который выполнится после авторизации
$cookies->add(new Cookie([
'name' => 'redirect',
'value' => yii::$app->request->pathInfo
]));
if (Yii::$app->request->isPost) {
// POST-запрос
// Настройка
Yii::$app->response->format = Response::FORMAT_JSON;
// Генерация ответа
Yii::$app->response->content = json_encode([
'main' => $this->renderPartial('/account/index'),
'redirect' => yii::$app->request->pathInfo,
'_csrf' => Yii::$app->request->getCsrfToken()
]);
} else if (Yii::$app->request->isGet) {
// GET-запрос
$this->redirect('/authentication');
}
}
public function actionIndex(string $prod, string $catn): array|string|null public function actionIndex(string $prod, string $catn): array|string|null
{ {
if ($model = Product::searchByCatnAndProd($catn, $prod)) { if ($model = Product::searchByCatnAndProd($catn, $prod)) {
@ -210,34 +241,34 @@ class ProductController extends Controller
/** /**
* Чтение товаров * Чтение товаров
* *
* @param string $stts Статус * @param string $type Тип
* @param int $page Страница
* *
* @return string|array|null * @return string|array|null
*/ */
public function actionRead(string $stts = 'all'): string|array|null public function actionRead(string $type = 'all', int $page = 1): string|array|null
{ {
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
// Инициализация входных параметров // Инициализация входных параметров
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 50; $amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 20;
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC']; $order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
// Инициализация cookie // Инициализация cookie
$cookies = yii::$app->response->cookies; $cookies = yii::$app->response->cookies;
// Инициализация аккаунта // Инициализация аккаунта
$account ?? $account = Account::initAccount(); $account ?? $account = Account::initAccount();
// Чтение товаров // Чтение товаров
$products = Product::read(where: $stts === 'all' || $stts === 'inactive' ? [] : ['stts' => $stts], limit: $amount, order: $order); $products = Product::read(where: $type === 'all' || $type === 'inactive' ? [] : ['stts' => $type], limit: $amount, page: $page, order: $order);
// Запись формата ответа // Запись формата ответа
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'products' => $this->renderPartial('/product/list', compact('account', 'products')), 'products' => $this->renderPartial('/product/list', compact('account', 'products', 'amount', 'page')),
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} }
@ -250,15 +281,15 @@ class ProductController extends Controller
* *
* @param string $catn Артикул * @param string $catn Артикул
*/ */
public function actionDelete(string $catn, string $prod): array|string|null public function actionDelete(string $prod, string $catn): array|string|null
{ {
// Инициализация буфера ответа // Инициализация буфера ответа
$return = [ $return = [
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
if (empty($catn)) { if (empty($catn) || empty($prod)) {
// Не получен артикул // Не получен артикул или производитель
// Запись кода ответа // Запись кода ответа
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
@ -355,8 +386,8 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
if (empty($catn)) { if (empty($catn) || empty($prod)) {
// Не получен артикул // Не получен артикул или производитель
// Запись кода ответа // Запись кода ответа
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
@ -368,20 +399,24 @@ class ProductController extends Controller
if ($from = Product::searchByCatnAndProd($catn, $prod)) { if ($from = Product::searchByCatnAndProd($catn, $prod)) {
// Найден товар // Найден товар
if ($target = yii::$app->request->post('catn') ?? yii::$app->request->get('catn')) { if (($target_catn = yii::$app->request->post('catn') ?? yii::$app->request->get('catn')) &&
// Инициализирован артикул товара для связи $target_prod = yii::$app->request->post('prod') ?? yii::$app->request->get('prod')) {
// Инициализирован артикул и производитель товара для связи
if ($to = Product::searchByCatn($target)) { foreach (explode(',', $target_catn, 50) as $analog) {
// Перебор аналогов
foreach (explode('/', $analog, 50) as $value) {
// Перебор аналогов (дополнительная фильтрация)
if ($to = Product::searchByCatnAndProd($value, $target_prod)) {
// Существуют товары к которым планируется соединение // Существуют товары к которым планируется соединение
} else { } else {
// Не существуют товары к которым планируется соединение // Не существуют товары к которым планируется соединение
// Инициализация товара // Инициализация товара
if ($to = [Product::writeEmpty((string) $target, $from->prod)]) { if ($to = [Product::writeEmpty($value, $target_prod, true)]) {
// Удалось записать товар // Удалось записать товар
// Запись в буфер возврата
$return['alert'] = "Записан новый товар: $target ($from->prod)";
} else { } else {
// Не удалось записать товар // Не удалось записать товар
@ -389,7 +424,7 @@ class ProductController extends Controller
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
// Запись в буфер возврата // Запись в буфер возврата
$return['alert'] = "Не удалось записать новый товар: $target ($from->prod)"; $return['alert'] = "Не удалось записать новый товар: $value ($target_prod)";
// Переход в конец алгоритма // Переход в конец алгоритма
goto end; goto end;
@ -399,18 +434,17 @@ class ProductController extends Controller
// Инициализация количества созданных рёбер // Инициализация количества созданных рёбер
$writed = 0; $writed = 0;
foreach ($to as $product) { foreach (is_array($to) ? $to : [$to] as $product) {
// Перебор товаров для записи связи: ТОВАР -> ТОВАР // Перебор товаров для записи связи: ТОВАР -> ТОВАР
// Универсализация данных (приведение к объекту) // Универсализация данных (приведение к объекту)
if (is_array($product) && !$product = Product::searchByCatnAndProd($product['catn'], $product['prod'])) continue; if (is_array($product) && !$product = Product::searchByCatnAndProd($product['catn'], $product['prod'])) continue;
// Запись ребра и синхронизация (добавление в группу к остальным аналогам) // Запись в группу
if (count($from->synchronization($product)) > 0) $writed++; if ($from->connect($product)) $writed++;
}
}
} }
// Запись в буфер возврата
$return['alert'] = "Создано $writed связей";
} }
} else { } else {
// Не найден товар // Не найден товар
@ -433,6 +467,13 @@ class ProductController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
// Запись в буфер возврата
$return['list'] = $this->renderPartial('analogs', [
'page' => 1,
'amount' => yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 30,
'model' => $from
]);
return $return; return $return;
} }
@ -462,7 +503,7 @@ class ProductController extends Controller
]; ];
if (empty($catn) || empty($prod)) { if (empty($catn) || empty($prod)) {
// Не получен артикул // Не получен артикул или производитель
// Запись кода ответа // Запись кода ответа
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
@ -471,17 +512,14 @@ class ProductController extends Controller
goto end; goto end;
} }
if ($from = Product::searchByCatnAndProd($catn, $prod)) { if ($target = Product::searchByCatnAndProd($catn, $prod)) {
// Товар найден // Товар найден
// Инициализация цели if ($target->disconnect()) {
$target = yii::$app->request->post('catn') ?? yii::$app->request->get('catn');
if ($from->disconnect($target)) {
// Удалено ребро (связь) // Удалено ребро (связь)
// Запись в буфер возврата // Запись в буфер возврата
$return['alert'] = "Продукты успешно отсоединены"; $return['disconnected'] = 1;
} else { } else {
// Не удалено ребро (связь) // Не удалено ребро (связь)
@ -489,7 +527,7 @@ class ProductController extends Controller
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
// Запись в буфер возврата // Запись в буфер возврата
$return['alert'] = "Не удалось отсоединить $target от $catn"; $return['alert'] = "Не удалось удалить $catn ($prod) из группы";
// Переход в конец алгоритма // Переход в конец алгоритма
goto end; goto end;
@ -499,7 +537,7 @@ class ProductController extends Controller
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
// Запись в буфер возврата // Запись в буфер возврата
$return['alert'] = "Не удалось найти товар от когорого требуется отсоединение: $catn"; $return['alert'] = "Не удалось найти товар $catn ($prod)";
// Переход в конец алгоритма // Переход в конец алгоритма
goto end; goto end;
@ -529,6 +567,64 @@ class ProductController extends Controller
} }
} }
/**
* Отключение аналога
*
* @param string $catn Артикул
*/
public function actionAnalogs(string $catn, string $prod, int $page): array|string|null
{
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn) || empty($prod)) {
// Не получен артикул или производитель
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
}
// Инициализация количества
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 30;
if ($model = Product::searchByCatnAndProd($catn, $prod)) {
// Товар найден
// Генерация списка
$list = trim($this->renderPartial('analogs', compact('model', 'page', 'amount')));
if (!empty($list)) $return['list'] = $list;
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар $catn ($prod) для инициализации аналогов";
// Переход в конец алгоритма
goto end;
}
// Конец алгоритма
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
// Переадресация на главную страницу
return $this->redirect("/");
}
public function actionEditTitle(string $catn, string $prod): array|string|null public function actionEditTitle(string $catn, string $prod): array|string|null
{ {
// Инициализация // Инициализация

View File

@ -50,7 +50,6 @@ class ProfileController extends Controller
'supplies', 'supplies',
'import', 'import',
'monitoring', 'monitoring',
'readGroups',
'geolocation-write', 'geolocation-write',
'panel-suppliers-requests-search', 'panel-suppliers-requests-search',
'imports-delete', 'imports-delete',
@ -428,7 +427,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
// Обработка настроек аккаунта // Обработка настроек аккаунта
if ($vars = yii::$app->request->post('Warehouse') ?? yii::$app->request->get('Warehouse') and $warehouse = yii::$app->request->post('_key') ?? yii::$app->request->get('_key')) { if ($vars = yii::$app->request->post('Warehouse') ?? yii::$app->request->get('Warehouse') and $warehouse = yii::$app->request->post('_key') ?? yii::$app->request->get('_key')) {
@ -461,7 +459,6 @@ class ProfileController extends Controller
return [ return [
'main' => $this->renderPartial('supplies', compact( 'main' => $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)), )),
@ -472,7 +469,6 @@ class ProfileController extends Controller
return $this->render('supplies', compact( return $this->render('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -495,7 +491,6 @@ class ProfileController extends Controller
$number = yii::$app->request->post('number') ?? yii::$app->request->get('number'); $number = yii::$app->request->post('number') ?? yii::$app->request->get('number');
$warehouse = yii::$app->request->post('warehouse') ?? yii::$app->request->get('warehouse'); $warehouse = yii::$app->request->post('warehouse') ?? yii::$app->request->get('warehouse');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// AJAX-POST-запрос // AJAX-POST-запрос
@ -540,7 +535,6 @@ class ProfileController extends Controller
return [ return [
'main' => $this->renderPartial('supplies', compact( 'main' => $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)), )),
@ -566,7 +560,6 @@ class ProfileController extends Controller
return [ return [
'main' => $this->renderPartial('supplies', compact( 'main' => $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)), )),
@ -581,7 +574,6 @@ class ProfileController extends Controller
return [ return [
'main' => $this->renderPartial('supplies', compact( 'main' => $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)), )),
@ -589,21 +581,6 @@ class ProfileController extends Controller
]; ];
} }
public static function readGroups()
{
// Инициализация
$groups = [];
foreach (SupplyGroup::read() as $group) {
// Перебор всех групп
// Генерация [КЛЮЧ => ИМЯ]
$groups[$group->_key] = $group->name;
}
return $groups;
}
/** /**
* Инициализация данных о геолокации * Инициализация данных о геолокации
* *
@ -887,7 +864,7 @@ class ProfileController extends Controller
/** /**
* Поиск заявок на регистрацию * Поиск заявок на регистрацию
*/ */
public function actionPanelSuppliersRequestsSearch() public function actionPanelSuppliersRequestsSearch(int $page = 1)
{ {
if (Yii::$app->request->isPost) { if (Yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -895,11 +872,15 @@ class ProfileController extends Controller
if (Account::isAdmin() || Account::isModer()) { if (Account::isAdmin() || Account::isModer()) {
// Доступ разрешен // Доступ разрешен
// Инициализация входных параметров
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 3;
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
// Инициализация буфера ответа // Инициализация буфера ответа
$response = []; $response = [];
// Поиск заявок на регистрацию // Поиск заявок на регистрацию
$suppliers = Account::searchSuppliersRequests(); $suppliers = Account::searchSuppliersRequests($amount, $page, $order);
foreach ($suppliers as $account) { foreach ($suppliers as $account) {
// Перебор заявок // Перебор заявок
@ -1025,7 +1006,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1062,6 +1042,8 @@ class ProfileController extends Controller
foreach (Supply::searchByImport($import->readId(), limit: 9999) as $supply) { foreach (Supply::searchByImport($import->readId(), limit: 9999) as $supply) {
// Перебор найденных поставок // Перебор найденных поставок
if (empty($supply)) continue;
if (ImportEdgeSupply::searchBySupply($supply)?->delete()) { if (ImportEdgeSupply::searchBySupply($supply)?->delete()) {
// Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА // Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА
@ -1071,7 +1053,7 @@ class ProfileController extends Controller
} }
// Отправка уведомления // Отправка уведомления
Notification::_write("Удалено $deleted поставок из инстанции поставки $_key", account: $account->_key); Notification::_write("Удалено $deleted поставок из инстанции поставки $_key", account: $account->readId());
if ($edge->delete()) { if ($edge->delete()) {
// Удалено ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВКИ // Удалено ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВКИ
@ -1080,7 +1062,7 @@ class ProfileController extends Controller
// Удалена инстанция поставки // Удалена инстанция поставки
// Отправка уведомления // Отправка уведомления
Notification::_write("Инстанция поставки $_key была удалена", account: $account->_key); Notification::_write("Инстанция поставки $_key была удалена", account: $account->readId());
} }
} }
} }
@ -1088,13 +1070,12 @@ class ProfileController extends Controller
// Не найдена инстанция поставки // Не найдена инстанция поставки
// Отправка уведомления // Отправка уведомления
Notification::_write("Не найдена инстанция поставки $_key", account: $account->_key); Notification::_write("Не найдена инстанция поставки $_key", account: $account->readId());
} }
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -1117,7 +1098,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1137,7 +1117,6 @@ class ProfileController extends Controller
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -1161,7 +1140,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1219,7 +1197,6 @@ class ProfileController extends Controller
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -1245,7 +1222,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1337,7 +1313,6 @@ class ProfileController extends Controller
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -1360,7 +1335,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1411,7 +1385,6 @@ class ProfileController extends Controller
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));
@ -1434,7 +1407,6 @@ class ProfileController extends Controller
$supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply')); $supply = new Supply(yii::$app->request->post('Supply') ?? yii::$app->request->get('Supply'));
$panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel'); $panel = yii::$app->request->post('panel') ?? yii::$app->request->get('panel');
$sidebar = $this->renderPartial('sidebar'); $sidebar = $this->renderPartial('sidebar');
$groups = self::readGroups();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
@ -1485,7 +1457,6 @@ class ProfileController extends Controller
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента
$return['main'] = $this->renderPartial('supplies', compact( $return['main'] = $this->renderPartial('supplies', compact(
'supply', 'supply',
'groups',
'sidebar', 'sidebar',
'panel' 'panel'
)); ));

View File

@ -106,7 +106,7 @@ class SearchController extends Controller
$return = [ $return = [
'timer' => $timer, 'timer' => $timer,
'panel' => $this->renderPartial('/search/loading'), 'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {
@ -159,6 +159,7 @@ class SearchController extends Controller
// Запись ответа // Запись ответа
$return = [ $return = [
'panel' => $this->renderPartial('/search/panel', compact('response', 'query')), 'panel' => $this->renderPartial('/search/panel', compact('response', 'query')),
'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
@ -167,7 +168,7 @@ class SearchController extends Controller
// Запись ответа // Запись ответа
$return['main'] = $this->renderPartial('/search/index', compact('response', 'query')); $return['main'] = $this->renderPartial('/search/index', compact('response', 'query'));
$return['hide'] = 1; $return['search_line_window_show'] = 1;
$return['redirect'] = '/search?type=product&q=' . $query; $return['redirect'] = '/search?type=product&q=' . $query;
} }
} }
@ -189,6 +190,7 @@ class SearchController extends Controller
return $return ?? [ return $return ?? [
'panel' => $this->renderPartial('/search/panel'), 'panel' => $this->renderPartial('/search/panel'),
'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken() '_csrf' => yii::$app->request->getCsrfToken()
]; ];
} else { } else {

View File

@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace app\controllers;
use yii;
use yii\web\Controller;
use yii\web\Response;
use yii\web\HttpException;
use yii\web\UploadedFile;
use yii\filters\AccessControl;
use app\models\Product;
use app\models\Settings;
use app\models\SupplyEdgeProduct;
use app\models\Supply;
use app\models\Account;
use app\models\Notification;
use app\models\OrderEdgeSupply;
use Exception;
class SupplyController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'actions' => [
'index',
]
],
[
'allow' => true,
'roles' => ['@'],
'actions' => []
],
[
'allow' => true,
'actions' => [
'read'
],
'matchCallback' => function ($rule, $action): bool {
if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) return true;
return false;
}
],
[
'allow' => false,
'roles' => ['?'],
'denyCallback' => [$this, 'accessDenied']
]
]
]
];
}
/**
* Чтение поставок
*
* @param int $page Страница
*
* @return string|array|null
*/
public function actionRead(int $page = 1): string|array|null
{
if (yii::$app->request->isPost) {
// POST-запрос
// Инициализация входных параметров
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 20;
$order = yii::$app->request->post('order') ?? yii::$app->request->get('order') ?? ['DESC'];
// Инициализация cookie
$cookies = yii::$app->response->cookies;
// Чтение поставок
$supplies = Supply::read(limit: $amount, page: $page, order: $order);
// Запись формата ответа
yii::$app->response->format = Response::FORMAT_JSON;
return [
'supplies' => $this->renderPartial('/supply/list', compact('supplies', 'amount', 'page')),
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return false;
}
}

View File

@ -0,0 +1,20 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m220808_185553_create_file_collection extends Migration
{
public function up()
{
/**
* @param string Название коллекции
* @param array Тип коллекции (2 - документ, 3 - ребро)
*/
$this->createCollection('file', ['type' => 2]);
}
public function down()
{
$this->dropCollection('file');
}
}

View File

@ -0,0 +1,20 @@
<?php
use mirzaev\yii2\arangodb\Migration;
class m220817_210350_create_import_edge_file_collection extends Migration
{
public function up()
{
/**
* @param string Название коллекции
* @param array Тип коллекции (2 - документ, 3 - ребро)
*/
$this->createCollection('import_edge_file', ['type' => 3]);
}
public function down()
{
$this->dropCollection('import_edge_file');
}
}

View File

@ -62,7 +62,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
'vrfy', 'vrfy',
'geol', 'geol',
'auth', 'auth',
'acpt' 'acpt',
'chpk'
] ]
); );
} }
@ -98,7 +99,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
'vrfy' => 'Статус подтверждения владением почты', 'vrfy' => 'Статус подтверждения владением почты',
'geol' => 'Геолокация', 'geol' => 'Геолокация',
'auth' => 'Аутентификационный хеш', 'auth' => 'Аутентификационный хеш',
'acpt' => 'Согласие с офертой' 'acpt' => 'Согласие с офертой',
'chpk' => 'Ключ для смены пароля'
] ]
); );
} }
@ -212,6 +214,59 @@ class Account extends Document implements IdentityInterface, PartnerInterface
return false; return false;
} }
/**
* Отправка сообщения для генерации нового пароля
*/
public static function restoreSend(string $mail): bool
{
if (($account = self::findByMail($mail)) instanceof self) {
// Найден аккаунт
// Запись ключа для аутентификации
$account->chpk = yii::$app->security->generateRandomString();
if ($account->update()) {
// Удалось обновить аккаунт
// Отправка письма
yii::$app->mail_system->compose()
->setFrom(yii::$app->params['mail']['system'])
->setTo($account->mail)
->setSubject('Подтвердите сброс пароля')
->setHtmlBody(yii::$app->controller->renderPartial('/mails/restore', ['id' => $account->_key, 'chpk' => $account->chpk]))
->send();
return true;
}
}
return false;
}
/**
* Генерация нового пароля
*/
public function restoreGenerate(): void
{
// Удаление ключа
$this->chpk = null;
// Генерация пароля
$this->pswd = self::passwordGenerate();
if ($this->update()) {
// Удалось обновить аккаунт
// Отправка письма
yii::$app->mail_system->compose()
->setFrom(yii::$app->params['mail']['system'])
->setTo($this->mail)
->setSubject('Генерация пароля')
->setHtmlBody(yii::$app->controller->renderPartial('/mails/password', ['pswd' => $this->pswd]))
->send();
}
}
/** /**
* Чтение полей для экспорта из 1С * Чтение полей для экспорта из 1С
*/ */
@ -669,18 +724,15 @@ class Account extends Document implements IdentityInterface, PartnerInterface
edge: 'account_edge_supply', edge: 'account_edge_supply',
direction: 'OUTBOUND', direction: 'OUTBOUND',
subquery_where: [ subquery_where: [
[
'account_edge_supply._from == account._id'
],
[ [
'account_edge_supply._to == "' . $_id . '"' 'account_edge_supply._to == "' . $_id . '"'
] ]
], ],
subquery_select: 'account', subquery_select: 'account',
where: 'account_edge_supply[0]._id != null', where: 'account_edge_supply[0]._id != null',
limit: 1, select: 'account_edge_supply[0]',
select: 'account_edge_supply[0]' limit: 1
)[0]; )[0] ?? null;
} }
/** /**
@ -918,9 +970,9 @@ class Account extends Document implements IdentityInterface, PartnerInterface
* *
* @return array * @return array
*/ */
public static function searchSuppliersRequests(): array public static function searchSuppliersRequests(int $amount = 3, int $page, array $order = ['DESC']): array
{ {
return self::find()->where(['agnt' => true, 'type' => 'requested'])->orderBy(['DESC'])->all(); return self::find()->where(['agnt' => true, 'type' => 'requested'])->limit($amount)->offset($amount * ($page - 1))->orderBy($order)->all();
} }
/** /**
@ -928,12 +980,12 @@ class Account extends Document implements IdentityInterface, PartnerInterface
* *
* @param static|null $account Аккаунт * @param static|null $account Аккаунт
*/ */
public static function initAccount(Account|int $account = null): ?static public static function initAccount(Account|string|int $account = null): ?static
{ {
if (is_null($account)) { if (is_null($account)) {
// Данные аккаунта не переданы // Данные аккаунта не переданы
if (yii::$app->user->isGuest) { if (empty(yii::$app->user) || yii::$app->user->isGuest) {
// Аккаунт не аутентифицирован // Аккаунт не аутентифицирован
} else { } else {
// Аккаунт аутентифицирован // Аккаунт аутентифицирован
@ -955,6 +1007,15 @@ class Account extends Document implements IdentityInterface, PartnerInterface
if ($account = Account::searchById(Account::collectionName() . "/$account")) { if ($account = Account::searchById(Account::collectionName() . "/$account")) {
// Удалось инициализировать аккаунт // Удалось инициализировать аккаунт
return $account;
}
} else if (is_string($account)) {
// Передан идентификатор документа (_id) (подразумевается)
// Инициализация (поиск в базе данных)
if ($account = Account::searchById($account)) {
// Удалось инициализировать аккаунт
return $account; return $account;
} }
} }

View File

@ -168,9 +168,9 @@ abstract class Document extends ActiveRecord
/** /**
* Чтение записей по максимальному ограничению * Чтение записей по максимальному ограничению
*/ */
public static function read(?array $where = [], int $limit = 100, ?array $order = null): array public static function read(?array $where = [], int $limit = 100, int $page = 1, ?array $order = null): array
{ {
return static::find()->where($where)->orderby($order)->limit($limit)->all(); return static::find()->where($where)->orderby($order)->limit($limit)->offset(($page - 1) * $limit)->all();
} }
/** /**

View File

@ -167,7 +167,7 @@ abstract class Edge extends Document
/** /**
* Поиск рёбер по направлению * Поиск рёбер по направлению
*/ */
public static function searchByDirection(string $_from, string $direction = 'OUTBOUND', string $type = '', array $where = [], int $limit = 1): static|array|null public static function searchByDirection(string $_from, string $direction = 'OUTBOUND', string $type = '', array $where = [], int $limit = 1, int $page = 1): static|array|null
{ {
if (str_contains($direction, 'OUTBOUND')) { if (str_contains($direction, 'OUTBOUND')) {
// Исходящие рёбра // Исходящие рёбра
@ -204,7 +204,7 @@ abstract class Edge extends Document
} else if (str_contains($direction, 'ANY')) { } else if (str_contains($direction, 'ANY')) {
// Исходящие и входящие рёбра // Исходящие и входящие рёбра
return static::searchByDirection(_from: $_from, direction: 'OUTBOUND', type: $type, where: $where, limit: $limit) + static::searchByDirection(_from: $_from, direction: 'INBOUND', type: $type, where: $where, limit: $limit); return static::searchByDirection(_from: $_from, direction: 'OUTBOUND', type: $type, where: $where, limit: $limit, page: $page) + static::searchByDirection(_from: $_from, direction: 'INBOUND', type: $type, where: $where, limit: $limit, page: $page);
} }
if ($limit < 2) { if ($limit < 2) {
@ -214,7 +214,7 @@ abstract class Edge extends Document
} else { } else {
// Несколько рёбер // Несколько рёбер
return $query->limit($limit)->all(); return $query->limit($limit)->offset($limit * ($page - 1))->all();
} }
} }
} }

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace app\models;
use ArangoDBClient\Document as ArangoDBDocument;
use app\models\traits\SearchByEdge;
use app\models\ImportEdgeFile;
class File extends Document
{
use SearchByEdge;
public static function collectionName(): string
{
return 'file';
}
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
'type',
'path',
'name',
'user',
'stts',
'meta'
]
);
}
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
'type' => 'Тип файла',
'path' => 'Относительный от хранилища путь до файла',
'name' => 'Название файла',
'user' => 'Пользователь управляющий файлом',
'stts' => 'Статус',
'meta' => 'Метаданные'
]
);
}
/**
* Перед сохранением
*
* @todo Подождать обновление от ебаного Yii2 и добавить
* проверку типов передаваемых параметров
*/
public function beforeSave($data): bool
{
if (parent::beforeSave($data)) {
if ($this->isNewRecord) {
if ($this->type = 'supplies excel') {
// Список поставок
$this->stts = 'needed to load';
}
}
return true;
}
return false;
}
public static function searchSuppliesNeededToLoad(int $amount = 3): array
{
return static::find()->where(['stts' => 'needed to load'])->limit($amount)->all();
}
/**
* Поиск по инстанции импорта
*
* @param Import $import Инстанция импорта
*/
public static function searchByImport(Import $import): ?File
{
return new File(self::searchByEdge(
from: 'import',
to: 'file',
subquery_where: [
[
'import._id' => $import->readId()
]
],
where: 'import_edge_file[0]._id != null',
select: 'file',
limit: 1
)[0]) ?? null;
}
}

View File

@ -79,9 +79,9 @@ class Import extends Document
* @param Warehouse $warehouse Инстанция склада * @param Warehouse $warehouse Инстанция склада
* @param int $limit Ограничение по максимальному количеству * @param int $limit Ограничение по максимальному количеству
* *
* @return array Инстанции испортов * @return array Инстанции импортов
*/ */
public static function searchByWarehouse(Warehouse $warehouse, int $limit = 10): array public static function searchByWarehouse(Warehouse $warehouse, int $limit = 10): ?array
{ {
return self::searchByEdge( return self::searchByEdge(
from: 'warehouse', from: 'warehouse',
@ -103,9 +103,9 @@ class Import extends Document
* @param Supply $supply Поставка * @param Supply $supply Поставка
* @param int $limit Ограничение по максимальному количеству * @param int $limit Ограничение по максимальному количеству
* *
* @return array Инстанции испортов * @return array Инстанции импортов
*/ */
public static function searchBySupply(Supply $supply, int $limit = 10): array public static function searchBySupply(Supply $supply, int $limit = 10): ?array
{ {
return self::searchByEdge( return self::searchByEdge(
from: 'supply', from: 'supply',
@ -120,4 +120,27 @@ class Import extends Document
limit: $limit limit: $limit
); );
} }
/**
* Поиск по файлу
*
* @param File $file Файл
*
* @return Import|null Инстанция импорта
*/
public static function searchByFile(File $file): ?Import
{
return self::searchByEdge(
from: 'file',
to: 'import',
edge: 'import_edge_file',
direction: 'OUTBOUND',
subquery_where: [
['import_edge_file._to' => $file->readId()],
['import_edge_supply.type' => 'connected']
],
where: 'import_edge_supply[0] != null',
limit: 1
)[0] ?? null;
}
} }

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace app\models;
use app\models\File;
use app\models\Import;
/**
* Связь инстанций импорта с поставками
*/
class ImportEdgeFile extends Edge
{
/**
* Имя коллекции
*/
public static function collectionName(): string
{
return 'import_edge_file';
}
/**
* Свойства
*/
public function attributes(): array
{
return array_merge(
parent::attributes(),
[
]
);
}
/**
* Метки свойств
*/
public function attributeLabels(): array
{
return array_merge(
parent::attributeLabels(),
[
]
);
}
/**
* Правила
*/
public function rules(): array
{
return array_merge(
parent::rules(),
[
]
);
}
/**
* Поиск по файлу
*
* @param File $file Файл
* @param int $limit Ограничение по максимальному количеству
*/
public static function searchByFile(File $file, int $limit = 1): array
{
return static::find()->where(['_to' => $file->readId(), 'type' => 'connected'])->limit($limit)->all();
}
/**
* Поиск по инстанции импорта
*
* @param Import $import Инстанция импорта
* @param int $limit Ограничение по максимальному количеству
*/
public static function searchByImport(Import $import, int $limit = 1): array
{
return static::find()->where(['_from' => $import->readId(), 'type' => 'connected'])->limit($limit)->all();
}
}

View File

@ -110,6 +110,6 @@ class ImportEdgeSupply extends Edge
*/ */
public static function searchBySupply(Supply $supply): ?static public static function searchBySupply(Supply $supply): ?static
{ {
return static::find()->where(['_to' => $supply->readId()])->one()[0] ?? null; return static::find()->where(['_to' => $supply->readId()])->one() ?? null;
} }
} }

View File

@ -147,16 +147,16 @@ class Notification extends Document
* *
* @param string $html Содержимое уведомления (HTML или текст) * @param string $html Содержимое уведомления (HTML или текст)
* @param bool|string|null $html Содержимое уведомления (HTML или текст) * @param bool|string|null $html Содержимое уведомления (HTML или текст)
* @param string $account Получатель уведомления (_key) * @param string $account Получатель уведомления (_key или "@...")
* @param string $type Тип уведомления * @param string $type Тип уведомления
* *
* @todo Намного удобнее будет заменить _key на _id, чтобы из рёбер сразу получать аккаунт без лишних операций * @todo Намного удобнее будет заменить _key на _id, чтобы из рёбер сразу получать аккаунт без лишних операций
*/ */
public static function _write(string $text, bool|string|null $html = false, string $account = null, string $type = self::TYPE_NOTICE): self|array|null public static function _write(string $text, bool|string|null $html = false, string $account = '', string $type = self::TYPE_NOTICE): self|array|null
{ {
// Инициализация // Инициализация
$model = new self; $model = new self;
$account ?? $account = yii::$app->user->identity->_key ?? throw new Exception('Не удалось инициализировать получателя'); $receiver = Account::initAccount($account)->_key ?? $account ?? throw new Exception('Не удалось инициализировать получателя');
if ((bool) (int) $html) { if ((bool) (int) $html) {
// Получен текст в формете HTML-кода // Получен текст в формете HTML-кода
@ -176,7 +176,7 @@ class Notification extends Document
// Уведомление записано // Уведомление записано
// Инициализация получателей и создание ребра // Инициализация получателей и создание ребра
self::searchReceiverAndConnect($model, $account, $type); self::searchReceiverAndConnect($model, $receiver, $type);
} }
return null; return null;

View File

@ -273,7 +273,8 @@ class Order extends Document implements DocumentInterface
bool $supplies = false, bool $supplies = false,
int|null $from = null, int|null $from = null,
int|null $to = null, int|null $to = null,
bool $count = false bool $count = false,
bool $debug = false
): int|array|null { ): int|array|null {
// Инициализация аккаунта // Инициализация аккаунта
if (empty($account)) { if (empty($account)) {
@ -350,9 +351,15 @@ class Order extends Document implements DocumentInterface
sort: ['DESC'], sort: ['DESC'],
select: $select, select: $select,
direction: 'INBOUND', direction: 'INBOUND',
count: !$supplies && $count count: !$supplies && $count,
debug: $debug
); );
if ($debug) {
var_dump($orders);
die;
}
if (!$supplies && $count) { if (!$supplies && $count) {
// Запрошен подсчет заказов // Запрошен подсчет заказов
@ -363,7 +370,7 @@ class Order extends Document implements DocumentInterface
$return = []; $return = [];
// Инициализация архитектуры буфера вывода // Инициализация архитектуры буфера вывода
foreach ($orders as $key => $order) { foreach ($orders ?? [null] as $key => $order) {
// Перебор заказов // Перебор заказов
// Запись в буфер возврата // Запись в буфер возврата
@ -526,6 +533,7 @@ class Order extends Document implements DocumentInterface
$buffer['delivery'] = $buffer_connection['data']; $buffer['delivery'] = $buffer_connection['data'];
} else { } else {
// Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord) // Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord)
$product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']); $product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']);
// Инициализация доставки Dellin (автоматическая) // Инициализация доставки Dellin (автоматическая)
@ -548,6 +556,7 @@ class Order extends Document implements DocumentInterface
$product->update(); $product->update();
} }
// Запись цены (цена поставки + цена доставки + наша наценка) // Запись цены (цена поставки + цена доставки + наша наценка)
$buffer['cost'] = ($supply->cost ?? $supply->onec['Цены']['Цена']['ЦенаЗаЕдиницу'] ?? throw new exception('Не найдена цена товара')) + ($buffer['delivery']['price']['all'] ?? $buffer['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0; $buffer['cost'] = ($supply->cost ?? $supply->onec['Цены']['Цена']['ЦенаЗаЕдиницу'] ?? throw new exception('Не найдена цена товара')) + ($buffer['delivery']['price']['all'] ?? $buffer['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0;
} catch (Exception $e) { } catch (Exception $e) {

View File

@ -457,7 +457,7 @@ class Product extends Document
where: 'supply_edge_product[0]._id != null', where: 'supply_edge_product[0]._id != null',
limit: 1, limit: 1,
select: 'supply_edge_product[0]' select: 'supply_edge_product[0]'
)[0]; )[0] ?? null;
} }
/** /**
@ -606,20 +606,6 @@ class Product extends Document
return false; return false;
} }
/**
* Деактивация
*
* @return bool Статус выполнения
*/
public function deactivate(): bool
{
$this->stts = 'inactive';
if ($this->update() > 0) return true;
return false;
}
/** /**
* Найти товары по группе * Найти товары по группе
* *
@ -645,4 +631,31 @@ class Product extends Document
select: 'product_edge_product_group[0]' select: 'product_edge_product_group[0]'
)[0]; )[0];
} }
/**
* Инициализация обложки для товара
*
* Ищет логотип в нужной категории (размере) для выбранного производителя
*
* @param string $prod Производитель
* @param int $size Размерная группа
*
* @return string Относительный путь до изображения от публичной корневой папки
*/
public static function cover(string $prod, int $size = 150): string
{
if ($size === 0) $size = '';
else $size = "h$size/";
// Инициализация пути
$path = "/img/covers/$size" . strtolower($prod);
// Поиск файла и возврат
if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.jpg')) return $path . '.jpg';
else if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.jpeg')) return $path . '.jpeg';
else if (file_exists(YII_PATH_PUBLIC . DIRECTORY_SEPARATOR . $path . '.png')) return $path . '.png';
// Возврат изображения по умолчанию
return "/img/covers/$size" . 'product.png';
}
} }

View File

@ -125,7 +125,7 @@ class ProductGroup extends Document implements GroupInterface
public function deleteProduct(Product $product): void public function deleteProduct(Product $product): void
{ {
// Удаление товара из группы (подразумевается, что будет только одно) // Удаление товара из группы (подразумевается, что будет только одно)
foreach (ProductEdgeProductGroup::searchByVertex($product->readId(), $this->readId(), filter: ['type' => 'member']) as $edge) $edge->delete; foreach (ProductEdgeProductGroup::searchByVertex($product->readId(), $this->readId(), filter: ['type' => 'member']) as $edge) $edge->delete();
// Запись в журнал // Запись в журнал
$this->journal('delete member', [ $this->journal('delete member', [
@ -138,9 +138,9 @@ class ProductGroup extends Document implements GroupInterface
* *
* @param int $limit Ограничение по максимальному количеству * @param int $limit Ограничение по максимальному количеству
*/ */
public function searchEdges(int $limit = 999): ?array public function searchEdges(int $limit = 100, int $page = 1): ?array
{ {
return ProductEdgeProductGroup::searchByDirection($this->readId(), 'INBOUND', where: ['type' => 'member'], limit: $limit); return ProductEdgeProductGroup::searchByDirection($this->readId(), 'INBOUND', where: ['type' => 'member'], limit: $limit, page: $page);
} }
/** /**
@ -148,12 +148,12 @@ class ProductGroup extends Document implements GroupInterface
* *
* @param int $limit Ограничение по максимальному количеству * @param int $limit Ограничение по максимальному количеству
*/ */
public function searchProducts(int $limit = 999): ?array public function searchProducts(int $limit = 100, int $page = 1): ?array
{ {
// Инициализация буфера товаров // Инициализация буфера товаров
$products = []; $products = [];
foreach ($this->searchEdges($limit) as $edge) { foreach ($this->searchEdges($limit, $page) as $edge) {
// Перебор рёбер // Перебор рёбер
$products[] = Product::searchById($edge->_from); $products[] = Product::searchById($edge->_from);
@ -191,6 +191,20 @@ class ProductGroup extends Document implements GroupInterface
return $transfered; return $transfered;
} }
/**
* Деактивация
*
* @return bool Статус выполнения
*/
public function deactivate(): bool
{
$this->stts = 'inactive';
if ($this->update() > 0) return true;
return false;
}
/** /**
* Запись рёбер групп * Запись рёбер групп
* *

View File

@ -129,7 +129,7 @@ class Search extends Document
// Инициализация буфера вывода // Инициализация буфера вывода
$response = $products; $response = $products;
// Генерация сдвига по запрашиваемым данным (пагинация) // Генерация сдвига по запрашиваемым данным (система страниц)
$offset = $limit * ($page - 1); $offset = $limit * ($page - 1);
foreach ($response as &$row) { foreach ($response as &$row) {
@ -167,7 +167,7 @@ class Search extends Document
// Инициализация буфера // Инициализация буфера
$buffer_connections = []; $buffer_connections = [];
if (count($connections) === 11) { if (count($connections) === 100) {
// Если в базе данных хранится много поставок // Если в базе данных хранится много поставок
// Инициализация // Инициализация
@ -418,7 +418,7 @@ class Search extends Document
// Не удалось использовать первое изображение как обложку // Не удалось использовать первое изображение как обложку
// Запись обложки по умолчанию // Запись обложки по умолчанию
$cover = '/img/covers/h150/product.png'; $cover = Product::cover($row['prod'], 150);
} }
} }

View File

@ -12,8 +12,10 @@ use app\models\Product;
use app\models\SupplyEdgeProduct; use app\models\SupplyEdgeProduct;
use app\models\Settings; use app\models\Settings;
use app\models\Import; use app\models\Import;
use app\models\File;
use app\models\ImportEdgeSupply; use app\models\ImportEdgeSupply;
use app\models\WarehouseEdgeImport; use app\models\WarehouseEdgeImport;
use app\models\ImportEdgeFile;
use carono\exchange1c\interfaces\OfferInterface; use carono\exchange1c\interfaces\OfferInterface;
use carono\exchange1c\interfaces\ProductInterface; use carono\exchange1c\interfaces\ProductInterface;
@ -23,6 +25,8 @@ use moonland\phpexcel\Excel;
use DateTime; use DateTime;
use DateTimeZone; use DateTimeZone;
use DatePeriod;
use DateInterval;
use Exception; use Exception;
@ -38,13 +42,6 @@ class Supply extends Product implements ProductInterface, OfferInterface
{ {
use Xml2Array; use Xml2Array;
/**
* Количество
*
* Используется при выводе в корзине
*/
public int $amnt = 0;
/** /**
* Имя коллекции * Имя коллекции
*/ */
@ -101,14 +98,21 @@ class Supply extends Product implements ProductInterface, OfferInterface
*/ */
public function afterSave($data, $vars): void public function afterSave($data, $vars): void
{ {
if (AccountEdgeSupply::searchByVertex(yii::$app->user->id, $this->readId())) { // Инициализация
$account = Account::initAccount();
if (isset($account)) {
// Инициализирован аккаунт
if (AccountEdgeSupply::searchByVertex($account->readId(), $this->readId())) {
// Ребро: "АККАУНТ -> ПОСТАВКА" уже существует // Ребро: "АККАУНТ -> ПОСТАВКА" уже существует
} else { } else {
// Ребра не существует // Ребра не существует
// Запись ребра: АККАУНТ -> ПОСТАВКА // Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write(yii::$app->user->id, $this->readId(), 'import'); (new AccountEdgeSupply)->write($account->readId(), $this->readId(), 'import');
}
} }
} }
@ -171,8 +175,13 @@ class Supply extends Product implements ProductInterface, OfferInterface
foreach (ImportEdgeSupply::find()->where(['_from' => $id])->limit($limit)->all() as $edge) { foreach (ImportEdgeSupply::find()->where(['_from' => $id])->limit($limit)->all() as $edge) {
// Перебор найденных рёбер // Перебор найденных рёбер
// Поиск поставки и запись в буфер вывода // Поиск поставки
$supplies[] = static::searchById($edge->_to); $supply = static::searchById($edge->_to);
if (empty($supply)) continue;
// Запись поставки в буфер вывода
$supplies[] = $supply;
} }
return $supplies; return $supplies;
@ -387,20 +396,15 @@ class Supply extends Product implements ProductInterface, OfferInterface
} }
/** /**
* Запись поставок из excel * Импорт Excel-файла
*
* На данный момент обрабатывает только импорт из
* файлов с расширением .excel
* *
* @param int $warehouse Идентификатор склада (_key) * @param int $warehouse Идентификатор склада (_key)
* @param Account|int|null $account Аккаунт * @param Account|int|null $account Аккаунт управляющий файлом и его данными
* @param bool $load Загрузить в базу данных
*/ */
public function importExcel(int $warehouse, Account|int|null $account = null): bool public function importExcel(int $warehouse, Account|int|null $account = null): bool
{ {
// Инициализация // Инициализация
$data = [];
$created = 0;
$updated = 0;
$account = Account::initAccount($account); $account = Account::initAccount($account);
if ($this->validate()) { if ($this->validate()) {
@ -424,10 +428,85 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (!file_exists($path)) if (!file_exists($path))
if (!mkdir($path, 0775, true)) if (!mkdir($path, 0775, true))
throw new Exception('Не удалось создать директорию', 500); throw new Exception('Не удалось создать директорию', 500);
$this->file_excel->saveAs($path = "$path/" . $this->file_excel->baseName . '.' . $this->file_excel->extension);
$this->file_excel->saveAs($path = "$path/" . $filename = $this->file_excel->baseName . '.' . $this->file_excel->extension); // Инициализация инстанции файла
$file = new File();
$data[] = Excel::import($path, [ // Запись настроек файла
$file->type = 'supplies excel';
$file->path = $path;
$file->name = $this->file_excel->baseName . '.' . $this->file_excel->extension;
$file->user = (int) $account->_key;
$file->meta = [
'warehouse' => $warehouse
];
// Запись в базу данных
$file->save();
// Инициализация инстанции импорта
$import = new Import;
if ($import->save()) {
// Записано в базу данных
if (ImportEdgeFile::write($import->readId(), $file->readId(), data: ['type' => 'connected'])) {
// Записано ребро: "ИНСТАНЦИЯ ИМПОРТА" -> ФАЙЛ
// Запись в журнал инстанции импорта
$import->journal('connect_with_file', [
'target' => $file->readId()
]);
}
if (WarehouseEdgeImport::write(Warehouse::collectionName() . '/' . $file->meta['warehouse'], $import->readId(), data: ['type' => 'loaded'])) {
// Записано ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВОК
// Запись в журнал инстанции импорта
$import->journal('connect_with_warehouse', [
'target' => Warehouse::collectionName() . '/' . $file->meta['warehouse']
]);
}
}
// Макрос действий после импорта
static::afterImportExcel($account, $warehouse);
return true;
}
// Запись ошибки
$this->addError('errors', 'Не пройдена проверка параметров');
return false;
}
/**
* Запись поставок из excel
*
* На данный момент обрабатывает только импорт из
* файлов с расширением .excel
*
* @param File $file Инстанция файла
*
* @return bool
*/
public static function loadExcel(File $file): bool {
// Инициализация
$created = 0;
$updated = 0;
$account = Account::initAccount((int) $file->user);
$data = [];
$supply = new Supply();
$supply->scenario = $supply::SCENARIO_IMPORT_EXCEL;
$supply->file_excel = $file->path;
if ($supply->validate()) {
// Пройдена проверка
$data[] = Excel::import($supply->file_excel, [
'setFirstRecordAsKeys' => true, 'setFirstRecordAsKeys' => true,
'setIndexSheetByName' => true, 'setIndexSheetByName' => true,
]); ]);
@ -441,7 +520,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (count($data) < 1) { if (count($data) < 1) {
// Не найдены строки с товарами // Не найдены строки с товарами
$this->addError('errors', 'Не удалось найти данные товаров'); $supply->addError('errors', 'Не удалось найти данные товаров');
} else { } else {
// Найдены строки с товарами // Найдены строки с товарами
@ -489,10 +568,11 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (!$product = Product::searchByCatnAndProd($article, $prod)) $product = Product::writeEmpty($article, $prod, Account::isMinimalAuthorized($account)); if (!$product = Product::searchByCatnAndProd($article, $prod)) $product = Product::writeEmpty($article, $prod, Account::isMinimalAuthorized($account));
// Инициализация группы товаров // Инициализация группы товаров
if (!$group = ProductGroup::searchByProduct($product)) $group = ProductGroup::writeEmpty(active: true); // if (!$group = ProductGroup::searchByProduct($product)) $group = ProductGroup::writeEmpty(active: true);
// Инициализация функции создания поставки // Инициализация функции создания поставки
$create = function (string $_supply, int|null $amount = null) use ($group, $row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool { // $create = function (string $_supply, int|null $amount = null) use ($group, $row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool {
$create = function (string $_supply, int|null $amount = null) use ($row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool {
// Очистка // Очистка
$_supply = trim($_supply); $_supply = trim($_supply);
@ -504,8 +584,272 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Запись артикула (каталожного номера) в буфер // Запись артикула (каталожного номера) в буфер
$_row['catn'] = $_supply; $_row['catn'] = $_supply;
// Запись цены в буфер
$_row['cost'] = (float) preg_replace('/[^\d\.]+/', '', preg_replace('/\,+/', ' ', (string) ($row['Стоимость'] ?? $row['стоимость'] ?? $row['Цена'] ?? $row['цена'] ?? $row['Cost'] ?? $row['cost'] ?? $row['Price'] ?? $row['price']))) ?? 0; $_row['cost'] = (float) preg_replace('/[^\d\.]+/', '', preg_replace('/\,+/', ' ', (string) ($row['Стоимость'] ?? $row['стоимость'] ?? $row['Цена'] ?? $row['цена'] ?? $row['Cost'] ?? $row['cost'] ?? $row['Price'] ?? $row['price']))) ?? 0;
$_row['prod'] = $prod;
// Запись производителя в буфер
$_row['prod'] = match($prod = ucwords(strtolower(preg_replace('/[^A-zА-я\s]/', ' ', $prod, 300)))) {
'Akebono' => 'AKEBONO',
'Bosch' => 'BOSCH',
'Brembo' => 'BREMBO',
'Ctr' => 'CTR',
'Delphi' => 'DELPHI',
'Gates' => 'GATES',
'Hella' => 'HELLA',
'Kyb' => 'KYB',
'Mando' => 'MANDO',
'Mk Kachiyama', 'Mk-kachiyama', 'Mk-Kachiyama' => 'MK KACHIYAMA',
'Narva' => 'NARVA',
'Ngk' => 'NGK',
'Nk' => 'NK',
'Osram' => 'OSRAM',
'Philips' => 'PHILIPS',
'Rancho' => 'RANCHO',
'Sangsin' => 'SANGSIN',
'Swag' => 'SWAG',
'Trw' => 'TRW',
'Filtron' => 'FILTRON',
'Ajusa' => 'AJUSA',
'Denso' => 'DENSO',
'Dolz' => 'DOLZ',
'Era' => 'ERA',
'Febest' => 'FEBEST',
'Freccia' => 'FRECCIA',
'Gmb' => 'GMB',
'Gsp' => 'GSP',
'Honda' => 'HONDA',
'Hyundai/kia', 'Hyundai/Kia', 'Hyundai-kia', 'Hyundai-Kia' => 'HYUNDAI/KIA',
'Knecht/mahle', 'Knecht/Mahle', 'Knecht-mahle', 'Knecht-Mahle' => 'KNECHT/MAHLE',
'Lynx' => 'LYNX',
'Mann' => 'MANN',
'Mitsubishi' => 'MITSUBISHI',
'Nipparts' => 'NIPPARTS',
'Nissan' => 'NISSAN',
'Sasic' => 'SASIC',
'Snr' => 'SNR',
'Subaru' => 'SUBARU',
'Toyota' => 'TOYOTA',
'Trw_moto', 'Trw_Moto', 'Trw-moto', 'Trw-Moto' => 'TRW_MOTO',
'Vag' => 'VAG',
'Wynns' => 'WYNNS',
'Ert' => 'ERT',
'Dongil' => 'DONGIL',
'Febi' => 'FEBI',
'Ford' => 'FORD',
'Injin' => 'INJIN',
'Land Rover' => 'LAND ROVER',
'Lpr' => 'LPR',
'Luzar' => 'LUZAR',
'MercedesBenz', 'Mercedesbenz', 'Mercedes Benz' => 'MERCEDES BENZ',
'Suzuki' => 'SUZUKI',
'Teikin' => 'TEIKIN',
'Blue print' => 'BLUE PRINT',
'Daihatsu' => 'DAIHATSU',
'Goodwill' => 'GOODWILL',
'Ina' => 'INA',
'Iveco' => 'IVECO',
'Jp Group' => 'JP GROUP',
'Mfilter' => 'MFILTER',
'Moog' => 'MOOG',
'Nibk' => 'NIBK',
'Asakashi', 'Js Asakashi' => 'JS Asakashi',
'Lemforder' => 'LEMFORDER',
'Man' => 'MAN',
'Volvo' => 'VOLVO',
'Ctp', 'Costex Tractor Parts', => 'CTP',
'Czd' => 'CZD',
'Lavr' => 'LAVR',
'Wai' => 'WAI',
'Asam' => 'ASAM',
'Bmw' => 'BMW',
'Brisk' => 'BRISK',
'Fenox' => 'FENOX',
'Gm', 'General Motors' => 'GENERAL MOTORS',
'Idemitsu' => 'IDEMITSU',
'Krauf' => 'KRAUF',
'Liqui Moly', 'Lm' => 'LIQUI MOLY',
'Mazda' => 'MAZDA',
'Musashi' => 'MUSASHI',
'Seiken' => 'SEIKEN',
'Totachi' => 'TOTACHI',
'Trialli' => 'TRIALLI',
'Dong Feng', 'Dongfeng', 'Dongfeng Motors', 'Dfm' => 'DONGFENG',
'Miles' => 'MILES',
'Sidem' => 'SIDEM',
'Stellox' => 'STELLOX',
'Tokico' => 'TOKICO',
'Phc Valeo', 'Valeo Phc' => 'Valeo PHC',
'Точка Опоры' => 'ТОЧКА ОПОРЫ',
'Jikiu' => 'JIKIU',
'Lada' => 'LADA',
'Mitasu' => 'MITASU',
'Neolux' => 'NEOLUX',
'Pilenga' => 'PILENGA',
'Renault' => 'RENAULT',
'Startvolt' => 'STARTVOLT',
'Zic' => 'ZIC',
'Автодело' => 'АВТОДЕЛО',
'Berkut' => 'BERKUT',
'Fiat' => 'FIAT',
'Profix' => 'PROFIX',
'Sampa' => 'SAMPA',
'Topfils' => 'TOPFILS',
'Uaz', 'Уаз' => 'UAZ',
'Тольятти-тза', 'Тольятти-Тза', 'Тольятти Тза', 'Тольяттитза' => 'Тольятти-ТЗА',
'Aywiparts' => 'AYWIPARTS',
'Сatterpillar', 'Cat', 'Catterpillar Inc' => 'CAT',
'Difa', 'Дифа' => 'DIFA',
'Nisshimbo' => 'NISSHINBO',
'Ruei' => 'RUEI',
'Vic' => 'VIC',
'Auto-gur', 'Auto-Gur', 'Auto Gur' => 'AUTO-GUR',
'Daejin' => 'DAEJIN',
'Gbrake' => 'G-brake',
'Img' => 'IMG',
'Kortex' => 'KORTEX',
'Koyo' => 'KOYO',
'Peugeot/citroen', 'Peugeot/Citroen', 'Peugeot-citroen', 'Peugeot-Citroen' => 'PEUGEOT-CITROEN',
'Tangde' => 'TANGDE',
'Double-force', 'Double-Force', 'Doubleforce', 'Double Force' => 'DOUBLE FORCE',
'Just-drive', 'Just-Drive', 'Just Drive' => 'JUST DRIVE',
'Quattro-freni', 'Quattro-Freni', 'Quattro Freni' => 'QUATTRO FRENI',
'Roers Parts', 'Roers-parts', 'Roers-Parts' => 'ROTERS-PARTS',
'Tixona' => 'TIXONA',
'Zekkert' => 'ZEKKERT',
'Horex' => 'HOREX',
'Zevs', 'Зевс' => 'ZEVS',
'Aiko' => 'AIKO',
'Castrol' => 'CASTROL',
'Deko' => 'DEKO',
'Hyundai Xteer' => 'HYUNDAI XTEER',
'Dyg' => 'DYG',
'Nty' => 'NTY',
'Skf' => 'SKF',
'Alfi Parts' => 'ALFI PARTS',
'Fiestroco' => 'FIESTROCO',
'Golden Snail' => 'GOLDEN SNAIL',
'Ti-guar', 'Ti-Guar' => 'Ti-Guar',
'Cautex' => 'CAUTEX',
'Bm-motorsport', 'Bm-Motorsport' => 'BM-Motorsport',
'Маз', 'Maz' => 'MAZ',
'Брт' => 'БРТ',
'Гпз' => 'ГПЗ',
'Rm Terex' => 'Terex',
'Agm' => 'Agama',
'Tsn' => 'TSN',
'Igp', 'Isuzu Genuine Parts' => 'Isuzu',
'Etp', 'Europe Tractor Parts' => 'ETP',
'Cum' => 'Cummins',
'Xcmg' => 'XCMG',
'Sdlg' => 'SDLG',
'Xcmg-sdlg', 'Xcmg-Sdlg' => 'XCMG-SDLG',
'Vilitan' => 'ViliTan',
'Kato' => 'KATO',
'Bw' => 'Baldwin',
'Itr' => 'ITR',
'Ofm' => 'OFM',
'Kz' => 'Kezhang Mechanic',
'Dongfeng Cummins Engine Company', 'Dongfeng Cummins' => 'DCEC',
'Bomag' => 'BOMAG',
'Shehwa' => 'SHEHWA',
'Kmp Brand', 'Kmp' => 'KMP',
'Jd' => 'John Deere',
'World Gasket', 'Wg', 'World-gasket', 'World-Gasket' => 'WG',
'Original Equipment Manufacturer', 'Oem' => 'OEM',
'Case' => 'CASE',
'Jf Filtration', 'Jf' => 'JF Filtration',
'Hp' => 'HP',
'Ярославский Завод Дизельной Аппаратуры', 'Язда' => 'ЯЗДА',
'Prc' => 'PRC',
'Sf', 'Sf Filter' => 'SF Filter',
'Ga' => 'GA',
'Kentek' => 'KENTEK',
'Cgr Ghinassi', 'Gb Group' => 'Ghinassi',
'Doosan' => 'DOOSAN',
'Dressta' => 'DRESSTA',
'Ammann' => 'AMMANN',
'Blumaq' => 'BLUMAQ',
'Deutz' => 'DEUTZ',
'Daewoo' => 'DAEWOO',
'Furukawa' => 'FURUKAWA',
'Niipon' => 'NIIPON',
'Cargo' => 'CARGO',
'Wps' => 'WPS',
'Zaufer' => 'ZAUFER',
'Handok' => 'HANDOK',
'Fg Wilson', 'F.G.Wilson', 'F.g.wilson' => 'F.G.WILSON',
'Racor' => 'RACOR',
'Jcb' => 'JCB',
'Auger' => 'AUGER',
'Ekka' => 'EKKA',
'Berco' => 'BERCO',
'Bosch Rexroth' => 'BOSCH REXROTH',
'Byg' => 'BYG',
'Cgr' => 'CGR',
'Dayco' => 'DAYCO',
'Dcf' => 'DCF',
'Dunlop' => 'DUNLOP',
'Febi' => 'FeBi',
'Fleetguard' => 'FLEETGUARD',
'Foton' => 'FOTON',
'Getlf' => 'GETLF',
'Hidromek' => 'HIDROMEK',
'Mann&Hummel', 'Mann&hummel' => 'MANN&HUMMEL',
'Niitsu' => 'NIITSU',
'Nok' => 'NOK',
'Nsk' => 'NSK',
'Ntn' => 'NTN',
'Orme' => 'ORME',
'Parker-racor', 'Parker-Racor' => 'PARKER-RACOR',
'Sanz' => 'SANZ',
'Sem' => 'SEM',
'Separ' => 'SEPAR',
'Shaanxi' => 'SHAANXI',
'Shell' => 'SHELL',
'Skf' => 'SKF',
'Ssp' => 'SSP',
'Sst' => 'SST',
'Stal' => 'STAL',
'Steyr' => 'STEYR',
'Toyota' => 'TOYOTA',
'Tt' => 'Tesla Technics',
'Vag' => 'VAG',
'Wix', 'Wix Filters' => 'WIX',
'Yuchai' => 'Yuchai',
'Zexel' => 'ZEXEL',
'Dongil' => 'DONGIL',
'Sanlux' => 'SANLUX',
'Amt' => 'AMT',
'Dt' => 'DT',
'Pramo' => 'PRAMO',
'Haffen' => 'HAFFEN',
'Mfilter' => 'MFILTER',
'Hengst' => 'HENGST',
'Sofima' => 'SOFIMA',
'Dolz' => 'DOLZ',
'Element' => 'ELEMENT',
'Sardes' => 'SARDES',
'Seintex' => 'SEINTEX',
'Patron' => 'PATRON',
'Lpr' => 'LPR',
'Kaichin' => 'KAICHIN',
'Union' => 'UNION',
'Rb-exide', 'Rb-Exide' => 'RB-EXIDE',
'Kurin' => 'KURIN',
'Topfils' => 'TOPFILS',
'Knor' => 'KNOR',
'Megapower' => 'MEGAPOWER',
'Red Filter', 'Redfilter' => 'REDFilter',
'Skania' => 'SKANIA',
'Sct' => 'SCT',
'Kolbenschmidt' => 'KOLBENSCHMIDT',
'Big Filter' => 'BIG Filter',
'Alco Filter' => 'ALCO Filter',
default => $prod
};
// Запись аналогов в буфер
$_row['oemn'] = array_walk($analogs, 'trim'); $_row['oemn'] = array_walk($analogs, 'trim');
// Инициализация буфера поставки // Инициализация буфера поставки
@ -660,6 +1004,9 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Перенос данных в буфер (существующий в базе данных дубликат) // Перенос данных в буфер (существующий в базе данных дубликат)
$supply->setAttributes($vars, false); $supply->setAttributes($vars, false);
// Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write($account->readId(), $supply->readId(), 'import');
// Перезапись существующего документа // Перезапись существующего документа
$supply->update(); $supply->update();
@ -672,7 +1019,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Проверка не пройдена // Проверка не пройдена
// Добавление ошибок // Добавление ошибок
foreach ($supply->errors as $attribute => $error) $this->addError($attribute, $error); foreach ($supply->errors as $attribute => $error) $supply->addError($attribute, $error);
// Запись статуса об ошибке // Запись статуса об ошибке
$error = true; $error = true;
@ -689,7 +1036,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
// } // }
// Добавление в группу аналогов // Добавление в группу аналогов
$group->writeProduct($product); // $group->writeProduct($product);
} }
return !$error; return !$error;
@ -698,12 +1045,12 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Запись поставки // Запись поставки
$create($article, (int) $amount); $create($article, (int) $amount);
foreach ($analogs as $_supply) { // foreach ($analogs as $_supply) {
// Перебор аналогов (если найдены) // // Перебор аналогов (если найдены)
// Запись поставки // // Запись поставки
$create((string) $_supply); // $create((string) $_supply);
} // }
} }
} }
} }
@ -712,27 +1059,15 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (count($imported) > 0) { if (count($imported) > 0) {
// Успешно записана минимум 1 поставка // Успешно записана минимум 1 поставка
// Инициализация инстанции импорта
$import = new Import;
$import->file = $path;
$import->name = $filename;
if ($import->save()) {
// Инстанция импорта успешно загружена
if (WarehouseEdgeImport::write(Warehouse::collectionName() . "/$warehouse", $import->readId(), data: ['type' => 'loaded'])) {
// Записано ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВОК
// Запись в журнал инстанции импорта
$import->journal('connect_with_warehouse', [
'target' => Warehouse::collectionName() . "/$warehouse"
]);
}
foreach ($imported as $supply) { foreach ($imported as $supply) {
// Перебор импортированных поставок // Перебор импортированных поставок
// Инициализация инстанции импорта
$import = Import::searchByFile($file);
if (isset($import)) {
// Найдена интанция импорта
if (ImportEdgeSupply::write($import->readId(), $supply->readId(), data: ['type' => 'imported', 'vrsn' => ImportEdgeSupply::searchMaxVersion($supply) + 1])) { if (ImportEdgeSupply::write($import->readId(), $supply->readId(), data: ['type' => 'imported', 'vrsn' => ImportEdgeSupply::searchMaxVersion($supply) + 1])) {
// Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА // Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА
@ -745,23 +1080,25 @@ class Supply extends Product implements ProductInterface, OfferInterface
} }
} }
// Макрос действий после импорта // Запись статуса
static::afterImportExcel($created, $updated); $file->stts = 'loaded';
if ($file->update() > 0) {
// Удалось записать в базу данных
// Запись в журнал
$file->journal('loaded');
}
// Макрос действий после загрузки
static::afterLoadExcel($account, $created, $updated);
// Удаление (важно именно задать null для формы в представлении) // Удаление (важно именно задать null для формы в представлении)
$this->file_excel = null; $supply->file_excel = null;
return true;
} }
// Запись ошибки // Запись ошибки
$this->addError('errors', 'Не пройдена проверка параметров'); $supply->addError('errors', 'Не пройдена проверка параметров');
// Макрос действий после импорта
static::afterImportExcel($created, $updated);
// Удаление (важно именно задать null для формы в представлении)
$this->file_excel = null;
return false; return false;
} }
@ -974,16 +1311,59 @@ class Supply extends Product implements ProductInterface, OfferInterface
} }
/** /**
* Вызывается после загрузки поставок из excel-документа * Вызывается после загрузки excel-документа на сервер
* *
* @param static|null $account Аккаунт
* @param string|int $warehouse Склад
*
* @return bool Статус выполнения
*/
public static function afterImportExcel(Account|int $account = null, string|int $warehouse = 'неизвестен'): bool
{
// Инициализация аккаунта
$account = Account::initAccount($account);
// Отправка уведомления о загрузке
$save = Notification::_write("Загружены товары для склада \"$warehouse\"", account: $account->_key);
// Инициализация периода
$period = new DatePeriod(new DateTime('@' . strtotime("00:00:00")), new DateInterval('PT5M'), new DateTime('@' . strtotime("next day 00:00:00")));
foreach($period as $date){
// Перебор периодов
if (($converted = $date->format('U')) > $time = time()) {
// Найден интервал из будущего времени (предполагается, что ближайший по причине остановки выполнения далее)
// Запись даты
$date = (new DateTime('@' . ($converted - $time)))->format('H:i:s');
break;
}
}
if ($date instanceof DateTime) $date = '5:00';
// Отправка уведомления об обработке
$handle = Notification::_write("Следующее обновление товаров начнётся через $date", account: $account->_key);
return $save && $handle;
}
/**
* Вызывается после загрузки поставок из excel-документа в базу данных
*
* @param static|null $account Аккаунт
* @param int $created Количество созданных документов * @param int $created Количество созданных документов
* @param int $updated Количество обновлённых документов * @param int $updated Количество обновлённых документов
*
* @return bool Статус выполнения
*/ */
public static function afterImportExcel(int $created = 0, int $updated = 0): bool public static function afterLoadExcel(Account|int $account = null, int $created = 0, int $updated = 0): bool
{ {
// Инициализация параметров // Инициализация параметров
$model = new Notification; $model = new Notification;
$account = yii::$app->user->identity; $account = Account::initAccount($account);
// Инициализация часового пояса // Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone); preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
@ -993,8 +1373,9 @@ class Supply extends Product implements ProductInterface, OfferInterface
$date = (new DateTime('now', new DateTimeZone($timezone)))->format('H:i d.m.Y'); $date = (new DateTime('now', new DateTimeZone($timezone)))->format('H:i d.m.Y');
// Настройка // Настройка
$model->text = yii::$app->controller->renderPartial('@app/views/notification/system/afterImportExcel', compact('created', 'updated', 'date')); $model->text = yii::$app->controller->renderPartial('@app/views/notification/system/afterLoadExcel', compact('created', 'updated', 'date'));
$model->type = $model::TYPE_NOTICE; $model->type = $model::TYPE_NOTICE;
$model->account = $account->readId();
// Отправка // Отправка
return (bool) $model->write(); return (bool) $model->write();

View File

@ -67,7 +67,7 @@ class Dellin extends Model
* *
* @param int $from Идентификатор терминала Dellin * @param int $from Идентификатор терминала Dellin
* @param int $to Идентификатор терминала Dellin * @param int $to Идентификатор терминала Dellin
* @param int $weight Вес (г) * @param int $weight Вес (кг)
* @param int $x Ширина () * @param int $x Ширина ()
* @param int $y Высота () * @param int $y Высота ()
* @param int $z Длинна () * @param int $z Длинна ()
@ -101,9 +101,6 @@ class Dellin extends Model
$y /= 100; $y /= 100;
$z /= 100; $z /= 100;
// Конвертация из граммов в килограммы
$weight /= 1000;
// Вычисление самой крупной стороны, так как ДеловыеЛинии имеют ограничения на все три поля и у длинны оно больше всех // Вычисление самой крупной стороны, так как ДеловыеЛинии имеют ограничения на все три поля и у длинны оно больше всех
if ($x > $z && $x > $y) { if ($x > $z && $x > $y) {
// "X" больше всех // "X" больше всех
@ -295,6 +292,7 @@ class Dellin extends Model
if (is_null($account)) { if (is_null($account)) {
// Данные аккаунта не переданы // Данные аккаунта не переданы
if (isset(yii::$app->user)) {
if (yii::$app->user->isGuest) { if (yii::$app->user->isGuest) {
// Аккаунт не аутентифицирован // Аккаунт не аутентифицирован
@ -305,6 +303,9 @@ class Dellin extends Model
// Инициализация // Инициализация
$account = yii::$app->user->identity; $account = yii::$app->user->identity;
} }
} else {
return 0;
}
} else { } else {
if (is_int($account)) { if (is_int($account)) {
// Передан идентификатор (_key) аккаунта (подразумевается) // Передан идентификатор (_key) аккаунта (подразумевается)

View File

@ -70,7 +70,7 @@ use app\models\AccountForm;
<?= $form->field($model, 'mail', ['enableLabel' => false, 'options' => ['class' => 'mb-2'], 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->textInput(['autofocus' => true, 'placeholder' => $model->getAttributeLabel('mail')]) ?> <?= $form->field($model, 'mail', ['enableLabel' => false, 'options' => ['class' => 'mb-2'], 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->textInput(['autofocus' => true, 'placeholder' => $model->getAttributeLabel('mail')]) ?>
<?= $form->field($model, 'pswd', ['enableLabel' => false, 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->passwordInput(['placeholder' => $model->getAttributeLabel('pswd')]) ?> <?= $form->field($model, 'pswd', ['enableLabel' => false, 'inputOptions' => ['class' => 'form-control button_clean'], 'errorOptions' => ['class' => 'help-block help-block-error px-2 small']])->passwordInput(['placeholder' => $model->getAttributeLabel('pswd')]) ?>
<div class="d-flex mb-2 mt-4"> <div class="d-flex mb-2 mt-3">
<?= Html::submitButton('Войти', ['name' => 'submitAuthentication', 'onclick' => 'authentication(this.parentElement.parentElement, \'' . $target . '\');', 'class' => 'flex-grow-1 mr-2 btn btn-primary button_clean']) ?> <?= Html::submitButton('Войти', ['name' => 'submitAuthentication', 'onclick' => 'authentication(this.parentElement.parentElement, \'' . $target . '\');', 'class' => 'flex-grow-1 mr-2 btn btn-primary button_clean']) ?>
<?= $form->field($model, 'auto', ['checkboxTemplate' => '<div class="checkbox button_clean">{beginLabel}' . <?= $form->field($model, 'auto', ['checkboxTemplate' => '<div class="checkbox button_clean">{beginLabel}' .
Html::submitButton('{labelTitle}', ['name' => 'submit', 'data-toggle' => 'button', 'class' => 'w-100 btn btn-primary button_clean', 'aria-pressed' => 'false', 'onclick' => 'return authentication_auto_button_status_switch(this);']) . Html::submitButton('{labelTitle}', ['name' => 'submit', 'data-toggle' => 'button', 'class' => 'w-100 btn btn-primary button_clean', 'aria-pressed' => 'false', 'onclick' => 'return authentication_auto_button_status_switch(this);']) .
@ -78,6 +78,7 @@ use app\models\AccountForm;
</div> </div>
<?= Html::submitButton('Регистрация', ['name' => 'submitRegistration', 'onclick' => 'return registration_start(this.parentElement, \'' . $target . '\');', 'class' => 'col-12 ml-auto btn btn-success btn-sm button_clean']) ?> <?= Html::submitButton('Регистрация', ['name' => 'submitRegistration', 'onclick' => 'return registration_start(this.parentElement, \'' . $target . '\');', 'class' => 'col-12 ml-auto btn btn-success btn-sm button_clean']) ?>
<small class="d-flex mt-2"><a class="mx-auto text-dark" type="button" onclick="restore(this.parentElement.parentElement)">Восстановить пароль</a></small>
<?php <?php

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
use app\models\Product;
use app\models\Account;
use app\models\Settings;
// Инициализация счетчика аккаунтов
$i = $amount * ($page - 1);
// Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
?>
<?php if ($page > 1) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php foreach ($accounts ?? [] as $account) : ?>
<?php
foreach ($account->jrnl ?? [] as $jrnl) {
// Перебор записей в журнале
if ($jrnl['action'] === 'create') {
// Найдена дата создания
// Инициализация даты
$create = (new DateTime())->setTimestamp($jrnl['date'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?? 'Неизвестно';
// Выход из цикла
break;
}
}
?>
<div class="mb-3 row">
<div class="pr-0 col-auto"><?= ++$i ?>.</div>
<div class="pr-0 col overflow-hidden" title="ФИО">
<?= $account->name ?? 'Неизвестно' ?>
</div>
<div class="my-auto pr-0 col-auto" title="Псевдоним">
<?= $account->indx ?? '' ?>
</div>
<div class="my-auto pr-0 col-auto" title="Тип аккаунта">
<?= $account->agnt ? 'Поставщик' : 'Покупатель' ?>
</div>
<div class="mr-3 my-auto pr-0 col-2" title="Уровень авторизации">
<?= $account->type() ?>
</div>
<div class="my-auto pr-0 col-auto text-right" title="Дата регистрации">
<?= $create ?? 'Неизвестно' ?>
</div>
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" onclick="page_profile_supplies_delete()"></a>
</div>
<?php if ($i < count($accounts) + $amount * ($page - 1)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>

View File

@ -122,7 +122,7 @@ use DateTime;
<p title="Ориентировочно"><i class="mr-1 fas $icon"></i> <b>~</b>$days дн</p> <p title="Ориентировочно"><i class="mr-1 fas $icon"></i> <b>~</b>$days дн</p>
</div> </div>
<div class="col-2 my-auto mr-3 text-right"> <div class="col-2 my-auto mr-3 text-right">
{$supply['cost']} {$supply['currency']} {$supply['supply']->cost} {$supply['currency']}
</div> </div>
</div> </div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
@ -223,20 +223,12 @@ use DateTime;
<script src="/js/textarea.js" defer></script> <script src="/js/textarea.js" defer></script>
<script src="/js/cart.js" defer></script> <script src="/js/cart.js" defer></script>
<script src="/js/profile.js" defer></script> <script src="/js/profile.js" defer></script>
<script defer> <script>
if (document.readyState === "complete") { document.addEventListener('cart.loaded', function(e) {
cart_cost_calculate(); cart_cost_calculate();
cart_registration_entity_init(<?= $account['_key'] ?>); cart_registration_entity_init(<?= $account['_key'] ?>);
cart_registration_choose('cart_registration_entity', <?= $account['_key'] ?>); cart_registration_choose('cart_registration_entity', <?= $account['_key'] ?>);
} else { });
document.addEventListener('DOMContentLoaded', function() {
cart_cost_calculate();
cart_registration_entity_init(<?= $account['_key'] ?>);
cart_registration_choose('cart_registration_entity', <?= $account['_key'] ?>);
}, false);
}
</script> </script>

View File

@ -5,10 +5,10 @@ declare(strict_types=1);
$this->title = 'SkillParts'; $this->title = 'SkillParts';
?> ?>
<link href="/css/ticker.css" rel="stylesheet"> <link href="/css/hotline.css" rel="stylesheet">
<div id="page_index" class="mb-auto"> <div id="page_index" class="mb-auto">
<section class="info_panel mb-4 unselectable"> <section class="info_panel unselectable">
<div class="container h-100 d-flex flex-column justify-content-center"> <div class="container h-100 d-flex flex-column justify-content-center">
<h1 class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</h1> <h1 class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</h1>
<p class="ml-0 d-flex"> <p class="ml-0 d-flex">
@ -21,22 +21,22 @@ $this->title = 'SkillParts';
</div> </div>
</section> </section>
<section class="h-100 d-flex ticker unselectable"> <section id="hotline" class="py-4 hotline unselectable" data-hotline="true" data-hotline-step="1">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/cummins.png" alt="Cummins"> <article><img src="/img/logos/h32px/compressed/cummins.png" alt="Cummins"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/iveco.png" alt="Iveco"> <article><img src="/img/logos/h32px/compressed/iveco.png" alt="Iveco"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu"> <article><img src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/case.png" alt="Case"> <article><img src="/img/logos/h32px/compressed/case.png" alt="Case"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu"> <article><img src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland"> <article><img src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/perkins.png" alt="Perkins"> <article><img src="/img/logos/h32px/compressed/perkins.png" alt="Perkins"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere"> <article><img src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar"> <article><img src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shantui.png" alt="Shantui"> <article><img src="/img/logos/h32px/compressed/shantui.png" alt="Shantui"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG"> <article><img src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco"> <article><img src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA"> <article><img src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG"> <article><img src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG"></article>
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi"> <article><img src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi"></article>
</section> </section>
<section class="container mb-4"> <section class="container mb-4">
@ -80,5 +80,13 @@ $this->title = 'SkillParts';
</section> </section>
</div> </div>
<script src="/js/ticker.js" defer></script> <script src="/js/hotline.js" defer></script>
<script src="/js/text.js" defer></script> <script src="/js/text.js" defer></script>
<script>
document.addEventListener('hotline.loaded', function(e) {
// Загружена программа: "бегущая строка"
// Обработка HTML-документа и генерация бегущих строк
e.detail.hotline.preprocessing();
});
</script>

View File

@ -1,6 +1,7 @@
<?php <?php
use app\models\Settings; use app\models\Settings;
use app\models\Product;
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
@ -133,9 +134,7 @@ use app\models\Settings;
<tr> <tr>
<td style="text-align: center; border: solid; border-top: thick; border-left: thick;" colspan="1" rowspan="2" valign="center"><b></b></td> <td style="text-align: center; border: solid; border-top: thick; border-left: thick;" colspan="1" rowspan="2" valign="center"><b></b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="3" rowspan="2" valign="center"><b>Производитель</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="6" rowspan="2" valign="center"><b>Товар</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="3" rowspan="2" valign="center"><b>Товар</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Поставщик</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Количество</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Количество</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Цена</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Цена</b></td>
<td style="text-align: center; border: solid; border-top: thick; border-right: thick;" colspan="2" rowspan="2" valign="center"><b>Сумма</b></td> <td style="text-align: center; border: solid; border-top: thick; border-right: thick;" colspan="2" rowspan="2" valign="center"><b>Сумма</b></td>
@ -156,11 +155,13 @@ use app\models\Settings;
<?php foreach ($data['supplies'] as $prod => $supplies) : ?> <?php foreach ($data['supplies'] as $prod => $supplies) : ?>
<?php foreach ($supplies as $catn => $deliveries) : ?> <?php foreach ($supplies as $catn => $deliveries) : ?>
<?php foreach ($deliveries as $delivery => $supply) : ?> <?php foreach ($deliveries as $delivery => $supply) : ?>
<?php
// Инициализация названия
$name = Product::searchByCatn($catn)->name ?? 'Без названия';
?>
<tr> <tr>
<td style="text-align: center; border: solid; border-left: thick;" colspan="1" valign="center"><?= $row++ ?></td> <td style="text-align: center; border: solid; border-left: thick;" colspan="1" valign="center"><?= $row++ ?></td>
<td style="text-align: left; border: solid;" colspan="3" valign="center"><?= $prod ?></td> <td style="text-align: left; border: solid;" colspan="6" valign="center"><?= $prod ?> <?= $catn ?> <?= $name ?></td>
<td style="text-align: left; border: solid;" colspan="3" valign="center"><?= $catn ?></td>
<td style="text-align: left; border: solid;" colspan="2" valign="center"><?= $supply['account']['indx'] ?></td>
<td style="text-align: center; border: solid;" colspan="2" valign="center"><?= $supply['amount'] ?></td> <td style="text-align: center; border: solid;" colspan="2" valign="center"><?= $supply['amount'] ?></td>
<td style="text-align: center; border: solid;" valign="center"><?= $supply['cost'] * $supply['amount'] ?></td> <td style="text-align: center; border: solid;" valign="center"><?= $supply['cost'] * $supply['amount'] ?></td>
<td style="text-align: center; border: solid;" valign="center"><?= $supply['currency'] ?></td> <td style="text-align: center; border: solid;" valign="center"><?= $supply['currency'] ?></td>

View File

@ -50,6 +50,7 @@ AppAsset::register($this);
<menu class="col-auto col-lg-4 mb-0 d-flex justify-content-end"></menu> <menu class="col-auto col-lg-4 mb-0 d-flex justify-content-end"></menu>
</div> </div>
<div class="h-divider"></div> <div class="h-divider"></div>
<script src="/js/js.cookie.min.js" defer></script>
</header> </header>
<aside class="container mb-4"> <aside class="container mb-4">
@ -110,6 +111,7 @@ AppAsset::register($this);
</div> </div>
</footer> </footer>
<script src="/js/loading.js" defer></script>
<?php $this->endBody() ?> <?php $this->endBody() ?>
</body> </body>

View File

@ -0,0 +1,14 @@
<div style="padding: 0 14%;">
<div style="background: #fff;">
<a title="SkillParts" href="https://skillparts.ru">
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
</a>
</div>
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
<h3 style="text-align: center; margin-bottom: 30px;"><b>Новый пароль</b></h3>
<p style="margin: 0 40px; margin-bottom: 8px;">По вашему запросу сгенерирован новый пароль: <b><?= $pswd ?? 'ОШИБКА' ?></b></p>
</div>
<div style="background: #fff;">
<small>Если это были не вы свяжитесь с администрацией</small>
</div>
</div>

View File

@ -0,0 +1,15 @@
<div style="padding: 0 14%;">
<div style="background: #fff;">
<a title="SkillParts" href="https://skillparts.ru">
<img style="width: 150px;" src="https://skillparts.ru/img/logos/skillparts.png" alt="SkillParts">
</a>
</div>
<div style="background: #f0eefb; padding: 40px; margin: 30px 0;">
<h3 style="text-align: center; margin-bottom: 30px;"><b>Генерация нового пароля</b></h3>
<p style="margin: 0 40px; margin-bottom: 8px;">Только что был получен запрос на генерацию нового пароля для вашего аккаунта</p>
<a style="display: block; text-align: center;" href="https://skillparts.ru/restore/<?= $id ?? '' ?>/<?= $chpk ?? '' ?>">Подтвердить</a>
</div>
<div style="background: #fff;">
<small>Если это были не вы, проигнорируйте это письмо</small>
</div>
</div>

View File

@ -10,8 +10,6 @@
<a style="display: block; text-align: center;" href="https://skillparts.loc/suppliers/request">Повторная заявка</a> <a style="display: block; text-align: center;" href="https://skillparts.loc/suppliers/request">Повторная заявка</a>
</div> </div>
<div style="background: #fff;"> <div style="background: #fff;">
<small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small> <small>Если это были не вы свяжитесь с администрацией</small>
</br>
<small>Если это были не вы, проверьте безопасность ваших аккаунтов и свяжитесь с администрацией</small>
</div> </div>
</div> </div>

View File

@ -13,6 +13,6 @@
<div style="background: #fff;"> <div style="background: #fff;">
<small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small> <small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small>
</br> </br>
<small>Если это были не вы, проверьте безопасность ваших аккаунтов и свяжитесь с администрацией</small> <small>Если это были не вы свяжитесь с администрацией</small>
</div> </div>
</div> </div>

View File

@ -1,3 +1,10 @@
<?php
declare(strict_types=1);
use yii;
?>
<link href="/css/pages/offer.css" rel="stylesheet"> <link href="/css/pages/offer.css" rel="stylesheet">
<div id="page_offer" class="container mb-auto py-3"> <div id="page_offer" class="container mb-auto py-3">
@ -42,8 +49,10 @@
</div> </div>
</div> </div>
<?php if (!yii::$app->user->isGuest): ?>
<div class="row mb-0 justify-content-center"> <div class="row mb-0 justify-content-center">
<a class="col-auto text-center text-white btn button_blue button_clean" href="/offer/accept">Принять</a> <a class="col-auto text-center text-white btn button_blue button_clean" href="/offer/accept">Принять</a>
</div> </div>
<?php endif ?>
</article> </article>
</div> </div>

View File

@ -1,3 +1,10 @@
<?php
declare(strict_types=1);
use yii;
?>
<link href="/css/pages/offer.css" rel="stylesheet"> <link href="/css/pages/offer.css" rel="stylesheet">
<div id="page_offer" class="container mb-auto py-3"> <div id="page_offer" class="container mb-auto py-3">
@ -54,8 +61,10 @@
</div> </div>
</div> </div>
<?php if (!yii::$app->user->isGuest): ?>
<div class="row mb-0 justify-content-center"> <div class="row mb-0 justify-content-center">
<a class="col-auto text-center text-white btn button_blue button_clean" href="/offer/accept-suppliers">Принять</a> <a class="col-auto text-center text-white btn button_blue button_clean" href="/offer/accept-suppliers">Принять</a>
</div> </div>
<?php endif ?>
</article> </article>
</div> </div>

View File

@ -40,170 +40,13 @@ if (empty($window)) {
<label class="btn button_white mb-0" for="orders_panel_orders">Мои заказы</label> <label class="btn button_white mb-0" for="orders_panel_orders">Мои заказы</label>
</div> </div>
<input type="radio" id="orders_panel_moderation" name="main_panel" <?= $window === 'orders_panel_moderation' ? 'checked' : null ?> /> <input type="radio" id="orders_panel_moderation" name="main_panel" <?= $window === 'orders_panel_moderation' ? 'checked' : null ?> />
<article> <article id="orders_panel_moderation_list">
<div class="orders_panel_moderation_menu mb-3"> <div class="orders_panel_moderation_menu mb-3">
<label class="btn btn-sm button_white mb-0" onclick="return orders_read('@last', '@all', null, null, 'orders_panel_moderation');">Все</label> <label class="btn btn-sm button_white mb-0" onclick="return orders_read('@last', '@all', null, null, 'orders_panel_moderation');">Все</label>
<?php foreach(Order::statusListInRussian() as $id => $label) : ?> <?php foreach(Order::statusListInRussian() as $id => $label) : ?>
<label class="btn btn-sm button_white mb-0 ml-2" onclick="return orders_read('@last', '<?= $id ?>', null, null, 'orders_panel_moderation');"><?= $label ?></label> <label class="btn btn-sm button_white mb-0 ml-2" onclick="return orders_read('@last', '<?= $id ?>', null, null, 'orders_panel_moderation');"><?= $label ?></label>
<?php endforeach ?> <?php endforeach ?>
</div> </div>
<?php if (!empty($moderator_data)) : ?>
<?php foreach ($moderator_data as $moderator_data) : ?>
<div id="<?= $moderator_data['order']->_key ?>_panel" class="page_order_panel mb-2 py-3 px-4 rounded">
<h5 class="row mt-1 mb-3">
<?php
// Инициализация времени отправки заказа
$date = time();
foreach ($moderator_data['order']->jrnl as $entry) {
// Перебор записей в журнале
if ($entry['action'] === 'requested') {
// Найдена запись со временем запроса заказа
if (empty($date) || $date <= $entry['date']) {
// Буфер не инициализирован или в него записано более старое событие
// Запись в буфер
$date = $entry['date'];
}
}
}
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
// Конвертация данных из буфера
$date = [
'H:i' => (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('H:i'),
'm.d.Y' => (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('d.m.Y')
];
?>
<p class="col-auto ml-1 font-weight-bold">#<?= $moderator_data['order']->_key ?></p>
<p class="col-auto mr-1 font-weight-bold">
<span><small><small><?= $date['H:i'] ?? 'Неизвестно' ?></small></small></span>
<span><?= $date['m.d.Y'] ?? 'Неизвестно' ?></span>
</p>
</h5>
<div class="dropdown-divider mb-3"></div>
<div class="row px-3">
<div id="orders_panel_supplies_<?= $moderator_data['order']->_key ?>" class="col-3 unselectable">
<?php if (!empty($moderator_data['supplies'])) : ?>
<?php
// Инициализация счетчика поставок для отрисовки горизонтального разделителя
$count = 1;
?>
<?php foreach ($moderator_data['supplies'] as $prod => $list) : ?>
<?php foreach ($list as $catn => $deliveries) : ?>
<?php foreach ($deliveries as $delivery => $supply) : ?>
<?php
// Инициализация обложки
$covr = null;
foreach ($supply['product']->imgs ?? [] as $img) {
// Перебор изображений для обложки
if ($img['covr'] ?? false) {
// Обложка найдена
$covr = $img['h150'];
break;
}
}
if (is_null($covr)) {
// Обложка не инициализирована
if (!$covr = $supply['product']->imgs[0]['h150'] ?? false) {
// Не удалось использовать первое изображение как обложку
// Запись обложки по умолчанию
$covr = '/img/covers/h150/product.png';
}
}
if ($supply['amount'] > 0) {
// Пройдена проверка на количество поставок в заказе
if (Order::checkSuppliesStts($supply['edge'])) $status = "<span id=\"{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply_stts_indicator_icon\" class=\"ml-auto my-auto fas fa-check\"></span>";
else $status = '';
// Инициализация иконки
$icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
// Генерация HTML
echo <<<HTML
<a id="{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply" class="row mb-2 p-2 px-0 rounded row_supply" type="button" onclick="return orders_supply_edit('{$moderator_data['order']->_key}', '$prod', '$catn', '$delivery');">
<img class="col-auto px-0 h-100 img-fluid rounded" src="$covr" />
<p id="{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply_stts_indicator" class="col d-flex text-dark">
{$catn}
<small class="ml-2 my-auto fas $icon"></small>
$status
</p>
</a>
HTML;
}
?>
<?php if ($count++ < count($moderator_data['supplies'])) : ?>
<div class="dropdown-divider mb-2"></div>
<?php endif ?>
<?php endforeach ?>
<?php endforeach ?>
<?php endforeach ?>
<?php else : ?>
<div class="row">
<p>Поставки не найдены</p>
</div>
<?php endif ?>
</div>
<div id="orders_panel_edit_<?= $moderator_data['order']->_key ?>" class="px-4 col-6 d-flex flex-column">
<p class="my-auto">Выберите поставку</p>
</div>
<div id="orders_panel_info_<?= $moderator_data['order']->_key ?>" class="col-3 d-flex flex-column">
<?php $form = ActiveForm::begin([
'id' => 'form_profile_settings',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;',
'class' => 'row'
]
]);
// Инициализация ребра до заказа
$account_edge_order = AccountEdgeOrder::searchByOrder($moderator_data['order']->readId())[0];
?>
<?= $form->field($account_edge_order, 'stts', ['options' => ['class' => "mb-1 w-100"]])
->dropDownList(Order::statusListInRussian($account_edge_order->stts), [
'onChange' => "return orders_status_edit('{$moderator_data['order']->_key}', this);",
'data-old-value' => $account_edge_order->stts
])->label(false); ?>
<small class="d-block mb-1 w-100 text-center"><b>Покупатель будет уведомлён</b></small>
<?php ActiveForm::end(); ?>
</div>
</div>
<script defer>
document.addEventListener(
'DOMContentLoaded',
function() {
order_init('<?= $moderator_data['order']->_key ?>');
},
false
);
</script>
</div>
<?php endforeach ?>
<?php else : ?>
<div class="page_order_panel py-3 px-4 rounded">
<p class="text-center">Заказов нет</p>
</div>
<?php endif ?>
</article> </article>
<input type="radio" id="orders_panel_orders" name="main_panel" <?= $window === 'orders_panel_orders' ? 'checked' : null ?> /> <input type="radio" id="orders_panel_orders" name="main_panel" <?= $window === 'orders_panel_orders' ? 'checked' : null ?> />
@ -332,6 +175,41 @@ if (empty($window)) {
if (empty($time = $order_edge_supply[0]->time)) { if (empty($time = $order_edge_supply[0]->time)) {
// Не найден срок доставки (в днях) // Не найден срок доставки (в днях)
// if (empty($order_edge_supply[0]->dlvr['data'])) {
// // Не удалось рассчитать доставку
// // Инициализация времени
// $time = 0;
// } else {
// // Удалось рассчитать доставку
// // Инициализация даты отправки
// try {
// // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
// $delivery_send_date = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspSender'])->getTimestamp();
// } catch (Throwable $e) {
// // Взять данные из "pickup" (Дата передачи груза на адресе отправителя)
// $delivery_send_date = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['pickup'])->getTimestamp();
// }
// // Инициализация времени доставки
// try {
// // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
// // Оставлено на всякий случай для дальнейших разбирательств
// $delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $order_edge_supply[0]->dlvr['data']['orderDates']['giveoutFromOspReceiver'])->getTimestamp();
// } catch (Throwable $e) {
// // Инициализация даты отправки
// // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
// $delivery_converted = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspReceiver'])->getTimestamp();
// }
// $time = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24);
// }
if (empty($order_edge_supply[0]->dlvr['data'])) { if (empty($order_edge_supply[0]->dlvr['data'])) {
// Не удалось рассчитать доставку // Не удалось рассчитать доставку
@ -340,16 +218,8 @@ if (empty($window)) {
} else { } else {
// Удалось рассчитать доставку // Удалось рассчитать доставку
// Инициализация даты отправки preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
try { $timezone = $timezone[1][0];
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspSender'])->getTimestamp();
} catch (Throwable $e) {
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя)
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['pickup'])->getTimestamp();
}
// Инициализация времени доставки // Инициализация времени доставки
try { try {
@ -357,14 +227,13 @@ if (empty($window)) {
// Оставлено на всякий случай для дальнейших разбирательств // Оставлено на всякий случай для дальнейших разбирательств
$delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $order_edge_supply[0]->dlvr['data']['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); $delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $order_edge_supply[0]->dlvr['data']['orderDates']['giveoutFromOspReceiver'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y');
} catch (Throwable $e) { } catch (Throwable $e) {
// Инициализация даты отправки // Инициализация даты отправки
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) $delivery_converted = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspReceiver'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y');
$delivery_converted = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspReceiver'])->getTimestamp();
} }
$time = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24); $time = $delivery_converted;
} }
} }
@ -500,4 +369,12 @@ if (empty($window)) {
|| yii::$app->user->identity->type === 'moderator') || yii::$app->user->identity->type === 'moderator')
) : ?> ) : ?>
<script src="/js/orders_panel.js" defer></script> <script src="/js/orders_panel.js" defer></script>
<script>
document.addEventListener('orders_panel.loaded', function(e) {
// Загружена программа для работы с заказами
// Инициализация списка аналогов
order_init_list(8, 1);
});
</script>
<?php endif ?> <?php endif ?>

View File

@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
use yii;
use yii\bootstrap\ActiveForm;
use app\models\AccountEdgeOrder;
use app\models\Order;
use app\models\Settings;
?>
<?php if (!empty($moderator_data)) : ?>
<?php foreach ($moderator_data as $moderator_data) : ?>
<div id="<?= $moderator_data['order']->_key ?>_panel" class="page_order_panel mb-2 py-3 px-4 rounded">
<h5 class="row mt-1 mb-3">
<?php
// Инициализация времени отправки заказа
$date = time();
foreach ($moderator_data['order']->jrnl as $entry) {
// Перебор записей в журнале
if ($entry['action'] === 'requested') {
// Найдена запись со временем запроса заказа
if (empty($date) || $date <= $entry['date']) {
// Буфер не инициализирован или в него записано более старое событие
// Запись в буфер
$date = $entry['date'];
}
}
}
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
// Конвертация данных из буфера
$date = [
'H:i' => (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('H:i'),
'd.m.Y' => (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('d.m.Y')
];
?>
<p class="col-auto ml-1 font-weight-bold">#<?= $moderator_data['order']->_key ?></p>
<p class="col-auto mr-1 font-weight-bold">
<span><small><small><?= $date['H:i'] ?? 'Неизвестно' ?></small></small></span>
<span><?= $date['d.m.Y'] ?? 'Неизвестно' ?></span>
</p>
</h5>
<div class="dropdown-divider mb-3"></div>
<div class="row px-3">
<div id="orders_panel_supplies_<?= $moderator_data['order']->_key ?>" class="col-3 unselectable">
<?php if (!empty($moderator_data['supplies'])) : ?>
<?php
// Инициализация счетчика поставок для отрисовки горизонтального разделителя
$count = 1;
?>
<?php foreach ($moderator_data['supplies'] as $prod => $list) : ?>
<?php foreach ($list as $catn => $deliveries) : ?>
<?php foreach ($deliveries as $delivery => $supply) : ?>
<?php
// Инициализация обложки
$covr = null;
foreach ($supply['product']->imgs ?? [] as $img) {
// Перебор изображений для обложки
if ($img['covr'] ?? false) {
// Обложка найдена
$covr = $img['h150'];
break;
}
}
if (is_null($covr)) {
// Обложка не инициализирована
if (!$covr = $supply['product']->imgs[0]['h150'] ?? false) {
// Не удалось использовать первое изображение как обложку
// Запись обложки по умолчанию
$covr = '/img/covers/h150/product.png';
}
}
if ($supply['amount'] > 0) {
// Пройдена проверка на количество поставок в заказе
if (Order::checkSuppliesStts($supply['edge'])) $status = "<span id=\"{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply_stts_indicator_icon\" class=\"ml-auto my-auto fas fa-check\"></span>";
else $status = '';
// Инициализация иконки
$icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
// Генерация HTML
echo <<<HTML
<a id="{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply" class="row mb-2 p-2 px-0 rounded row_supply" type="button" onclick="return orders_supply_edit('{$moderator_data['order']->_key}', '$prod', '$catn', '$delivery');">
<img class="col-auto px-0 h-100 img-fluid rounded" src="$covr" />
<p id="{$moderator_data['order']->_key}_{$prod}_{$catn}_{$delivery}_supply_stts_indicator" class="col d-flex text-dark">
{$catn}
<small class="ml-2 my-auto fas $icon"></small>
$status
</p>
</a>
HTML;
}
?>
<?php if ($count++ < count($moderator_data['supplies'])) : ?>
<div class="dropdown-divider mb-2"></div>
<?php endif ?>
<?php endforeach ?>
<?php endforeach ?>
<?php endforeach ?>
<?php else : ?>
<div class="row">
<p>Поставки не найдены</p>
</div>
<?php endif ?>
</div>
<div id="orders_panel_edit_<?= $moderator_data['order']->_key ?>" class="px-4 col-6 d-flex flex-column">
<p class="my-auto">Выберите поставку</p>
</div>
<div id="orders_panel_info_<?= $moderator_data['order']->_key ?>" class="col-3 d-flex flex-column">
<?php $form = ActiveForm::begin([
'id' => 'form_profile_settings',
'action' => false,
'fieldConfig' => [
'template' => '{label}{input}',
],
'options' => [
'onsubmit' => 'return false;',
'class' => 'row'
]
]);
// Инициализация ребра до заказа
$account_edge_order = AccountEdgeOrder::searchByOrder($moderator_data['order']->readId())[0];
?>
<?= $form->field($account_edge_order, 'stts', ['options' => ['class' => "mb-1 w-100"]])
->dropDownList(Order::statusListInRussian($account_edge_order->stts), [
'onChange' => "return orders_status_edit('{$moderator_data['order']->_key}', this);",
'data-old-value' => $account_edge_order->stts
])->label(false); ?>
<small class="d-block mb-1 w-100 text-center"><b>Покупатель будет уведомлён</b></small>
<?php ActiveForm::end(); ?>
</div>
</div>
<script defer>
document.addEventListener(
'DOMContentLoaded',
function() {
order_init('<?= $moderator_data['order']->_key ?>');
},
false
);
</script>
</div>
<?php endforeach ?>
<?php else : ?>
<div class="page_order_panel py-3 px-4 rounded">
<p class="text-center">Заказов нет</p>
</div>
<?php endif ?>

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use app\models\Search;
use app\models\ProductGroup;
// Инициализация аналогов
$analogs = Search::content(ProductGroup::searchByProduct($model)?->searchProducts((int) $amount, (int) $page) ?? []);
// Удаление товара из списка его аналогов
foreach($analogs as $key => $analog) if ($analog['_key'] === $model->_key) unset($analogs[$key]);
?>
<?php foreach($analogs as $analog) : ?>
<?php
// Инициализация данных товара
$covr = null;
$prod = $analog['prod'] ?? 'Неизвестно';
$catn = $analog['catn'] ?? 'Неизвестно';
$name = $analog['name'] ?? 'Без названия';
// Генерация списка товаров
$supplies_html = Search::generate($analog, $covr, analogs: true);
?>
<div class="col mb-2 rounded">
<div class="row p-2 rounded row_analog">
<img class="ml-0 rounded" src="<?= $covr ?>" />
<div class="col ml-3 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= urlencode($prod) ?>/<?= urlencode($catn) ?>">
<h5 class="m-0"><b><?= $prod ?></b> <?= $catn ?></h5>
<h6 class="m-0"><small><?= $name ?></small></h6>
</a>
</div>
<div class="col ml-3 p-0 d-flex flex-column">
<?= $supplies_html ?>
</div>
<?php if (!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')) : ?>
<button class="ml-3 col-2 btn btn-small button_red button_clean" onclick="return product_panel_disconnect('<?= $catn ?>', '<?= $prod ?>', this.parentElement.parentElement);">Отключить</button>
<?php endif ?>
</div>
</div>
<?php endforeach ?>

View File

@ -5,6 +5,8 @@ declare(strict_types=1);
use yii\bootstrap\ActiveForm; use yii\bootstrap\ActiveForm;
use app\models\Product; use app\models\Product;
use app\models\ProductGroup;
use app\models\Search;
?> ?>
@ -31,7 +33,7 @@ use app\models\Product;
// Перебор изображений для генерации обложек // Перебор изображений для генерации обложек
// Инициализация // Инициализация
$h150 = $image['h150'] ?? '/img/covers/h150/product.png'; $h150 = $image['h150'] ?? Product::cover($prod, 150);
if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) { if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) {
echo <<<HTML echo <<<HTML
@ -71,8 +73,7 @@ use app\models\Product;
// Перебор изображений для генерации полных версий // Перебор изображений для генерации полных версий
// Инициализация // Инициализация
$prod = $image['prod'] ?? 'Неизвестный'; $orig = $image['orig'] ?? Product::cover($model['prod'], 0);
$orig = $image['orig'] ?? '/img/covers/product.png';
if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) { if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) {
// Если это изображение является обложкой или обложка не найдена // Если это изображение является обложкой или обложка не найдена
@ -116,22 +117,6 @@ use app\models\Product;
?> ?>
</div> </div>
<div class="ml-4 px-3 d-flex flex-column w-50"> <div class="ml-4 px-3 d-flex flex-column w-50">
<?php if (!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')) : ?>
<nav class="row my-2 py-2 px-3 rounded product_admin_menu">
<a id="product_info_admin_connect" class="mr-2 h-100 text-dark d-flex" title="Подсоединить аналог" role="button" onclick="return product_panel_connect('<?= $model['catn'] ?>', '<?= $model['prod'] ?>');">
<i class="fas fa-plus-square my-auto"></i>
</a>
<a id="product_info_admin_connect" class="mr-2 h-100 text-dark d-flex" title="Отсоединить аналог" role="button" onclick="return product_panel_disconnect('<?= $model['catn'] ?>', '<?= $model['prod'] ?>');">
<i class="fas fa-minus-square my-auto"></i>
</a>
<a id="product_info_admin_connect" class="ml-auto h-100 text-dark d-flex" title="Удалить" role="button" onclick="return product_panel_delete('<?= $model['catn'] ?>', '<?= $model['prod'] ?>');">
<i class="fas fa-trash-alt my-auto"></i>
</a>
</nav>
<?php endif ?>
<div class="row mb-2"> <div class="row mb-2">
<?php if ( <?php if (
!yii::$app->user->isGuest !yii::$app->user->isGuest
@ -148,12 +133,15 @@ use app\models\Product;
<?= $model['prod'] ?? 'Неизвестно' ?> <?= $model['prod'] ?? 'Неизвестно' ?>
</h3> </h3>
<?php else : ?> <?php else : ?>
<h1 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto product_catn"> <h2 id="name_<?= $model['catn'] ?>" class="mt-auto my-0 w-100 mb-1 product_name">
<?= $model['catn'] ?> <?= $model['name'] ?? 'Неизвестно' ?>
</h1>
<h2 id="prod_<?= $model['catn'] ?>" class="mt-auto my-0 product_prod">
<?= $model['prod'] ?? 'Неизвестно' ?>
</h2> </h2>
<h1 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto pointer-event product_catn">
<?= $model['catn'] ?? '' ?>
</h1>
<h3 id="prod_<?= $model['catn'] ?>" class="mt-auto my-0 product_prod">
<?= $model['prod'] ?? 'Неизвестно' ?>
</h3>
<?php endif ?> <?php endif ?>
</div> </div>
@ -173,7 +161,7 @@ use app\models\Product;
</p> </p>
<!-- Вес --> <!-- Вес -->
<p class="form-control-sm p-0 h-auto"> <p class="form-control-sm p-0 h-auto">
<b>Вес:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1 pointer-event" role="button" onclick="return product_panel_weight_edit('<?= $model['catn'] ?>', '<?= $model['prod'] ?>', this);"><?= empty($model['wght']) ? '0' : $model['wght'] ?></span><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'] ?>', '<?= $model['prod'] ?>', this);"><?= empty($model['wght']) ? '0' : $model['wght'] ?></span><span class="mr-1">кг</span>
</p> </p>
</section> </section>
<?php else : ?> <?php else : ?>
@ -184,7 +172,7 @@ use app\models\Product;
</p> </p>
<!-- Вес --> <!-- Вес -->
<p class="form-control-sm p-0 h-auto"> <p class="form-control-sm p-0 h-auto">
<b>Вес:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1"><?= empty($model['wght']) ? '0' : $model['wght'] ?></span>г <b>Вес:</b><span id="prod_<?= $model['catn'] ?>_dmns_x" class="ml-1"><?= empty($model['wght']) ? '0' : $model['wght'] ?>кг</span>
</p> </p>
</section> </section>
<?php endif ?> <?php endif ?>
@ -201,7 +189,7 @@ use app\models\Product;
) : ?> ) : ?>
<p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break pointer-event product_description" role="button" onclick="return product_panel_description_edit('<?= $model['catn'] ?>', '<?= $model['prod'] ?>', this);"><?= $model['dscr'] ?? 'Без описания' ?></p> <p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break pointer-event product_description" role="button" onclick="return product_panel_description_edit('<?= $model['catn'] ?>', '<?= $model['prod'] ?>', this);"><?= $model['dscr'] ?? 'Без описания' ?></p>
<?php else : ?> <?php else : ?>
<p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break product_description"><?= $model['prod'] ?? 'Без описания' ?></p> <p id="description_<?= $model['catn'] ?>" class="mt-0 ml-0 text-break pointer-event product_description"><?= $model['dscr'] ?? 'Без описания' ?></p>
<?php endif ?> <?php endif ?>
</div> </div>
@ -211,6 +199,8 @@ use app\models\Product;
&& (yii::$app->user->identity->type === 'administrator' && (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator') || yii::$app->user->identity->type === 'moderator')
) { ) {
echo '<div class="row">';
// Инициализация артикула // Инициализация артикула
$catn = $model['catn']; $catn = $model['catn'];
$prod = $model['prod']; $prod = $model['prod'];
@ -219,15 +209,21 @@ use app\models\Product;
// Товар активен // Товар активен
echo <<<HTML echo <<<HTML
<button class="row btn button_red button_clean" onclick="return product_panel_product_stts('$catn', '$prod', 'inactive');">Деактивировать</button> <button class="col btn button_red button_clean" onclick="return product_panel_product_stts('$catn', '$prod', 'inactive');">Деактивировать</button>
HTML; HTML;
} else { } else {
// Товар неактивен, либо что-то с ним ещё // Товар неактивен, либо что-то с ним ещё
echo <<<HTML echo <<<HTML
<button class="row btn button_green button_clean" onclick="return product_panel_product_stts('$catn', '$prod', 'active');">Активировать</button> <button class="col btn button_green button_clean" onclick="return product_panel_product_stts('$catn', '$prod', 'active');">Активировать</button>
HTML; HTML;
} }
echo <<<HTML
<button class="ml-3 col-3 btn button_red button_clean" onclick="return product_panel_delete('{$model['catn']}', '{$model['prod']}');">Удалить</button>
HTML;
echo '</div>';
} }
?> ?>
</div> </div>
@ -238,6 +234,18 @@ use app\models\Product;
<time class="ml-auto"></time> <time class="ml-auto"></time>
</div> --> </div> -->
</div> </div>
<?php if (!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')) : ?>
<div class="my-3 p-3 d-flex rounded">
<input type="text" class="mr-2 col-2 form-control button_clean" title="Производитель" placeholder="Производитель" value="<?= $model['prod'] ?>">
<input type="text" class="mr-2 col form-control button_clean" title="Артикулы (через запятую)" placeholder="Артикулы (через запятую)">
<button class="col-3 btn button_green button_clean" onclick="return product_panel_connect('<?= $model['catn'] ?>', '<?= $model['prod'] ?>', this.parentElement.children[1].value, this.parentElement.children[0].value, 'products_list');">Подключить аналог</button>
</div>
<?php endif ?>
<div id="products_list" class="mt-2 rounded"></div>
</article> </article>
</div> </div>
</div> </div>
@ -250,3 +258,21 @@ use app\models\Product;
) : ?> ) : ?>
<script src="/js/product_panel.js" defer></script> <script src="/js/product_panel.js" defer></script>
<?php endif ?> <?php endif ?>
<script>
if (document.readyState === "complete") {
// Документ загружен
// Инициализация списка аналогов
panel_analogs_initializing('<?= $model['prod'] ?>', '<?= $model['catn'] ?>', 10, 1);
} else {
// Документ не загружен
// Обработчик события загрузки документа
document.addEventListener('DOMContentLoaded', function() {
// Обработчик события инициализации
// Инициализация списка аналогов
panel_analogs_initializing('<?= $model['prod'] ?>', '<?= $model['catn'] ?>', 10, 1);
}, false);
};
</script>

View File

@ -7,14 +7,18 @@ use app\models\Settings;
use DateTime; use DateTime;
use DateTimeZone; use DateTimeZone;
// Инициализация счетчика аккаунтов // Инициализация счетчика товаров
$amount = 0; $i = $amount * ($page - 1);
// Инициализация часового пояса // Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone); preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0]; $timezone = $timezone[1][0];
?> ?>
<?php if ($page > 1 && count($products) > 0) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<?php <?php
@ -34,8 +38,8 @@ $timezone = $timezone[1][0];
?> ?>
<div class="mb-3 row"> <div class="mb-3 row">
<div class="pr-0 col-auto"><?= ++$amount ?>.</div> <div class="pr-0 col-auto"><?= ++$i ?>.</div>
<a class="pr-0 col overflow-hidden" title="<?= $product->name ?? 'Артикул' ?>" href="/product/<?= $product->prod ?? 'Неизвестно' ?>/<?= $product->catn ?? 'Неизвестно' ?>"> <a class="pr-0 col overflow-hidden" title="<?= $product->name ?? 'Артикул' ?>" href="/product/<?= urlencode($product->prod ?? 'Неизвестно') ?>/<?= $product->catn ?? 'Неизвестно' ?>">
<?= $product->catn ?? 'Неизвестно' ?> <?= $product->catn ?? 'Неизвестно' ?>
<span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span> <span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span>
</a> </a>
@ -45,7 +49,7 @@ $timezone = $timezone[1][0];
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" onclick="page_profile_supplies_delete()"></a> <a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" onclick="page_profile_supplies_delete()"></a>
</div> </div>
<?php if ($amount < count($products)) : ?> <?php if ($i < count($products) + $amount * ($page - 1)) : ?>
<div class="dropdown-divider mb-3"></div> <div class="dropdown-divider mb-3"></div>
<?php endif ?> <?php endif ?>
<?php endforeach ?> <?php endforeach ?>

View File

@ -39,7 +39,7 @@ $timezone = $timezone[1][0];
<label class="btn button_white mb-0 mr-2" for="profile_panel_input_notifications" onclick="return page_profile_panel_choose('profile_panel_input_notifications');">Уведомления</label> <label class="btn button_white mb-0 mr-2" for="profile_panel_input_notifications" onclick="return page_profile_panel_choose('profile_panel_input_notifications');">Уведомления</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_input_accounts" onclick="return page_profile_panel_choose('profile_panel_input_accounts');">Аккаунты</label> <label class="btn button_white mb-0 mr-2" for="profile_panel_input_accounts" onclick="return page_profile_panel_choose('profile_panel_input_accounts');">Аккаунты</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_input_products" onclick="return page_profile_panel_choose('profile_panel_input_products');">Товары</label> <label class="btn button_white mb-0 mr-2" for="profile_panel_input_products" onclick="return page_profile_panel_choose('profile_panel_input_products');">Товары</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_input_supplies" onclick="return page_profile_panel_choose('profile_panel_input_supplies');">Поставки</label> <label class="btn button_white mb-0 mr-2" for="profile_panel_input_supplies" onclick="return page_profile_panel_choose('profile_panel_supplies_read');">Поставки</label>
<label class="btn button_white mb-0 mr-2" for="profile_panel_input_settings" onclick="return page_profile_panel_choose('profile_panel_input_settings');">Настройки</label> <label class="btn button_white mb-0 mr-2" for="profile_panel_input_settings" onclick="return page_profile_panel_choose('profile_panel_input_settings');">Настройки</label>
</div> </div>
<div class="profile_panel_content"> <div class="profile_panel_content">
@ -90,63 +90,14 @@ $timezone = $timezone[1][0];
<input type="radio" id="profile_panel_input_accounts" name="main_panel" <?= $panel === 'profile_panel_input_accounts' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_input_accounts" name="main_panel" <?= $panel === 'profile_panel_input_accounts' ? 'checked' : null ?> />
<div class="profile_panel_input_accounts_menu mb-4"> <div class="profile_panel_input_accounts_menu mb-4">
<label class="mb-0 mr-2 px-2 py-1 btn button_white_small" for="profile_panel_input_accounts_control" onclick="return page_profile_panel_accounts_choose('profile_panel_input_accounts_control');">Пользователи</label> <label class="mb-0 mr-2 px-2 py-1 btn button_white_small" for="profile_panel_input_accounts_control" onclick="return page_profile_panel_choose('profile_panel_input_accounts_control');">Пользователи</label>
<label class="mb-0 mr-2 px-2 py-1 btn button_white_small" for="profile_panel_input_accounts_suppliers" onclick="return page_profile_panel_accounts_choose('profile_panel_input_accounts_suppliers');">Поставщики</label> <label class="mb-0 mr-2 px-2 py-1 btn button_white_small" for="profile_panel_input_accounts_suppliers" onclick="return page_profile_panel_choose('profile_panel_input_accounts_suppliers');">Поставщики</label>
</div> </div>
<input type="radio" id="profile_panel_input_accounts_control" name="main_panel_accounts" <?= $panel_accounts === 'profile_panel_input_accounts_control' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_input_accounts_control" name="main_panel_accounts" <?= $panel_accounts === 'profile_panel_input_accounts_control' ? 'checked' : null ?> />
<div class="col"> <div class="col">
<h5>Список пользователей</h5> <h5>Список пользователей</h5>
<div class="dropdown-divider mb-4"></div> <div class="dropdown-divider mb-4"></div>
<div id="profile_panel_input_accounts_list" class="px-3"> <div id="profile_panel_input_accounts_list" class="px-3"></div>
<?php
// Инициализация счетчика аккаунтов
$amount = 0;
// Чтение аккаунтов
$accounts = Account::read(limit: 100, order: ['desc']);
?>
<?php foreach ($accounts ?? [] as $account) : ?>
<?php
foreach ($account->jrnl ?? [] as $jrnl) {
// Перебор записей в журнале
if ($jrnl['action'] === 'create') {
// Найдена дата создания
// Инициализация даты
$create = (new DateTime())->setTimestamp($jrnl['date'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?? 'Неизвестно';
// Выход из цикла
break;
}
}
?>
<div class="mb-3 row">
<div class="pr-0 col-auto"><?= ++$amount ?>.</div>
<div class="pr-0 col overflow-hidden" title="ФИО">
<?= $account->name ?? 'Неизвестно' ?>
</div>
<div class="my-auto pr-0 col-auto" title="Псевдоним">
<?= $account->indx ?? '' ?>
</div>
<div class="my-auto pr-0 col-auto" title="Тип аккаунта">
<?= $account->agnt ? 'Поставщик' : 'Покупатель' ?>
</div>
<div class="mr-3 my-auto pr-0 col-2" title="Уровень авторизации">
<?= $account->type() ?>
</div>
<div class="my-auto pr-0 col-auto text-right" title="Дата регистрации">
<?= $create ?? 'Неизвестно' ?>
</div>
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" onclick="page_profile_supplies_delete()"></a>
</div>
<?php if ($amount < count($accounts)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>
</div>
</div> </div>
<input type="radio" id="profile_panel_input_accounts_suppliers" name="main_panel_accounts" <?= $panel_accounts === 'profile_panel_input_accounts_suppliers' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_input_accounts_suppliers" name="main_panel_accounts" <?= $panel_accounts === 'profile_panel_input_accounts_suppliers' ? 'checked' : null ?> />
@ -159,10 +110,10 @@ $timezone = $timezone[1][0];
<input type="radio" id="profile_panel_input_products" name="main_panel" <?= $panel === 'profile_panel_input_products' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_input_products" name="main_panel" <?= $panel === 'profile_panel_input_products' ? 'checked' : null ?> />
<div class="col"> <div class="col">
<h5>Список товаров</h5> <h5>Список товаров</h5>
<div class="col-auto orders_panel_menu ml-auto text-right"> <div class="orders_panel_menu ml-auto text-right">
<a class="btn btn-sm button_white button_clean font-weight-bold mb-0 mr-2" type="button" onclick="return profile_panel_products_read('@last', 'all');">Все</a> <a class="btn btn-sm button_white button_clean font-weight-bold mb-0 mr-2" type="button" onclick="return profile_panel_products_read('@last', 'all', '@last', '@last', 1, true);">Все</a>
<a class="btn btn-sm button_white button_clean font-weight-bold mb-0 mr-2" type="button" onclick="return profile_panel_products_read('@last', 'active');">Активные</a> <a class="btn btn-sm button_white button_clean font-weight-bold mb-0 mr-2" type="button" onclick="return profile_panel_products_read('@last', 'active', '@last', '@last', 1, true);">Активные</a>
<a class="btn btn-sm button_white button_clean font-weight-bold mb-0 mr-2" type="button" onclick="return profile_panel_products_read('@last', 'inactive');">Неактивные</a> <a class="btn btn-sm button_white button_clean font-weight-bold mb-0" type="button" onclick="return profile_panel_products_read('@last', 'inactive', '@last', '@last', 1, true);">Неактивные</a>
</div> </div>
<div class="dropdown-divider mb-4"></div> <div class="dropdown-divider mb-4"></div>
<div id="profile_panel_input_products_wrap"></div> <div id="profile_panel_input_products_wrap"></div>
@ -172,76 +123,7 @@ $timezone = $timezone[1][0];
<div class="col"> <div class="col">
<h5>Список поставок</h5> <h5>Список поставок</h5>
<div class="dropdown-divider mb-4"></div> <div class="dropdown-divider mb-4"></div>
<?php <div id="profile_panel_input_supplies_wrap"></div>
// Инициализация счетчика аккаунтов
$amount = 0;
// Чтение аккаунтов
$supplies = Supply::read(limit: 100, order: ['desc']);
?>
<?php foreach ($supplies ?? [] as $supply) : ?>
<?php
try {
// Поиск аккаунта владельца
$account = '/account/' . Account::searchBySupplyId($supply->readId())['_key'];
// Деинициализация товара
unset($product);
if ($connect = $supply->searchConnectWithProduct()) {
// Найдена привязка поставки к аккаунту
$product = Product::searchById($connect->_to);
}
foreach ($supply->jrnl ?? [] as $jrnl) {
// Перебор записей в журнале
if ($jrnl['action'] === 'create') {
// Найдена дата создания
// Инициализация даты
$create = (new DateTime())->setTimestamp($jrnl['date'])->setTimezone(new DateTimeZone($timezone))->format('H:i d.m.Y') ?? 'Неизвестно';
// Выход из цикла
break;
}
}
} catch (Throwable $t) {
continue;
}
?>
<div class="mb-3 row">
<div class="pr-0 col-auto"><?= ++$amount ?>.</div>
<div class="pr-0 col-2 overflow-hidden" title="Артикул">
<?= $supply->catn ?? 'Неизвестно' ?>
<span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span>
</div>
<div class="my-auto pr-0 col">
</div>
<div class="my-auto pr-0 col-auto" title="Количество">
<?= $supply->amnt ?? '0' ?>шт
</div>
<div class="my-auto pr-0 col-auto" title="Стоимость">
<?= $supply->cost ?? '0' ?>р
</div>
<div class="my-auto pr-0 col-auto text-right" title="Дата создания">
<?= $create ?? 'Неизвестно' ?>
</div>
<?php if (empty($product)) : ?>
<a class="my-auto pr-0 col-auto fas fa-shopping-basket icon_red" title="Товар отсутствует" type="button" onclick="return profile_panel_input_suppliers_accounts_create_product(this, <?= "'$supply->catn'" ?? null ?>, <?= "'$supply->prod'" ?? null ?>);"></a>
<?php else : ?>
<a class="my-auto pr-0 col-auto fas fa-shopping-basket text-dark" title="Товар" href="<?= "/product/$product->prod/$product->catn" ?>"></a>
<?php endif ?>
<a class="my-auto pr-0 col-auto fas fa-user text-dark" title="Владелец" href="<?= $account ?? '/' ?>"></a>
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" title="Удалить" onclick="page_profile_supplies_delete()"></a>
</div>
<?php if ($amount < count($supplies)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>
</div> </div>
<input type="radio" id="profile_panel_input_settings" name="main_panel" <?= $panel === 'profile_panel_input_settings' ? 'checked' : null ?> /> <input type="radio" id="profile_panel_input_settings" name="main_panel" <?= $panel === 'profile_panel_input_settings' ? 'checked' : null ?> />
@ -362,12 +244,20 @@ $timezone = $timezone[1][0];
<script src="/js/product_panel.js" defer></script> <script src="/js/product_panel.js" defer></script>
<script src="/js/profile_panel.js" defer></script> <script src="/js/profile_panel.js" defer></script>
<script src="/js/textarea.js" defer></script> <script src="/js/textarea.js" defer></script>
<script defer> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('profile.loaded', function(e) {
// Загружен документ // Загружена программа для работы с профилем
});
document.addEventListener('profile_panel.loaded', function(e) {
// Загружена программа для работы с панелью управления
// Инициализация активной вкладки // Инициализация активной вкладки
page_profile_panel_choose('<?= $panel ?? '' ?>'); page_profile_panel_choose('<?= $panel ?? '' ?>');
});
document.addEventListener('textarea.loaded', function(e) {
// Загружена программа для работы с блоками текста
// Инициализация панели для ввода текста (тест уведомлений для администратора) // Инициализация панели для ввода текста (тест уведомлений для администратора)
initTextarea( initTextarea(
@ -394,5 +284,5 @@ $timezone = $timezone[1][0];
send.disabled = false; send.disabled = false;
send_html.disabled = false; send_html.disabled = false;
}); });
}, false); });
</script> </script>

View File

@ -9,6 +9,7 @@ use app\models\Account;
use app\models\Import; use app\models\Import;
use app\models\Settings; use app\models\Settings;
use app\models\Warehouse; use app\models\Warehouse;
use app\models\File;
use DateTime; use DateTime;
use DateTimeZone; use DateTimeZone;
@ -103,9 +104,12 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
} }
} }
// Инициализация файла
$file = File::searchByImport($import);
// Инициализация ссылки на скачивание // Инициализация ссылки на скачивание
preg_match_all('/\/files\/.*$/', $import->file, $matches); preg_match_all('/\/files\/.*$/', $file->path ?? '', $matches);
$download = $matches[0][0]; $download = $matches[0][0] ?? '';
?> ?>
<div class="mb-3 row"> <div class="mb-3 row">
@ -113,7 +117,7 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
<?= ++$amount ?>. <?= ++$amount ?>.
</div> </div>
<div class="col"> <div class="col">
<?= $import->name ?? 'Без названия' ?> <?= (isset($file->stts) && $file->stts === 'loaded') ? $file->name ?? 'Без названия' : ($file->name ?? 'Без названия') . ' (в очереди на загрузку)' ?>
</div> </div>
<div class="col-3 px-0 text-right"> <div class="col-3 px-0 text-right">
<?= $create ?? 'Неизвестно' ?> <?= $create ?? 'Неизвестно' ?>
@ -172,16 +176,19 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
<?php foreach (Import::searchByWarehouse($warehouse) as $import) : ?> <?php foreach (Import::searchByWarehouse($warehouse) as $import) : ?>
<?php <?php
// Инициализация файла
$file = File::searchByImport($import);
// Инициализация ссылки на скачивание // Инициализация ссылки на скачивание
preg_match_all('/\/files\/.*$/', $import->file, $matches); preg_match_all('/\/files\/.*$/', $file->path ?? '', $matches);
$download = $matches[0][0]; $download = $matches[0][0] ?? '';
?> ?>
<div class="mx-2 mb-3 row"> <div class="mx-2 mb-3 row">
<div class="col-auto"> <div class="col-auto">
<?= ++$amount_imports ?>. <?= ++$amount_imports ?>.
</div> </div>
<div class="col"> <div class="col">
<?= $import->name ?? 'Без названия' ?> <?= (isset($file->stts) && $file->stts === 'loaded') ? $file->name ?? 'Без названия' : ($file->name ?? 'Без названия') . ' (в очереди на загрузку)' ?>
</div> </div>
<a class="pr-0 my-auto col-auto fas fa-file-download text-dark" title="Скачать" href="<?= $download ?? '/#' ?>" aria-hidden="true" download></a> <a class="pr-0 my-auto col-auto fas fa-file-download text-dark" title="Скачать" href="<?= $download ?? '/#' ?>" aria-hidden="true" download></a>
<a class="my-auto col-auto fas fa-trash-alt text-dark" title="Удалить" type="button" aria-hidden="true" onclick="return page_profile_imports_delete(<?= $import->_key ?>);"></a> <a class="my-auto col-auto fas fa-trash-alt text-dark" title="Удалить" type="button" aria-hidden="true" onclick="return page_profile_imports_delete(<?= $import->_key ?>);"></a>

View File

@ -51,7 +51,7 @@ use app\models\Search;
<div class="row p-2 rounded"> <div class="row p-2 rounded">
<img class="ml-0 rounded" src="<?= $covr ?>" /> <img class="ml-0 rounded" src="<?= $covr ?>" />
<div class="col ml-3 p-0 d-flex flex-column row_fixed_height"> <div class="col ml-3 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= $prod ?>/<?= $catn ?>"> <a class="my-auto text-dark" href="/product/<?= urlencode($prod) ?>/<?= urlencode($catn) ?>">
<h5 class="m-0"><b><?= $prod ?></b> <?= $catn ?></h5> <h5 class="m-0"><b><?= $prod ?></b> <?= $catn ?></h5>
<h6 class="m-0"><small><?= $name ?></small></h6> <h6 class="m-0"><small><?= $name ?></small></h6>
</a> </a>
@ -83,7 +83,7 @@ use app\models\Search;
// Перебор найденных товаров товаров производителя // Перебор найденных товаров товаров производителя
// Чтение и запись аналогов // Чтение и запись аналогов
$analogs[$prod][$catn] = Search::content(products: ProductGroup::searchByProduct(Product::searchByCatnAndProd($catn, $prod))->searchProducts()); $analogs[$prod][$catn] = Search::content(products: ProductGroup::searchByProduct(Product::searchByCatnAndProd($catn, $prod))?->searchProducts() ?? []);
// Исключение из вывода в списке аналогов (проверка на дубликат) // Исключение из вывода в списке аналогов (проверка на дубликат)
$writed[$prod][$catn] = true; $writed[$prod][$catn] = true;
@ -98,8 +98,9 @@ use app\models\Search;
<?php foreach ($catns as $catn => $products) : ?> <?php foreach ($catns as $catn => $products) : ?>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<?php <?php
// Проверка на дубликат // Проверка на дубликат
if (array_key_exists($product['catn'], $writed[$prod])) continue; if (isset($writed[$prod]) && array_key_exists($product['catn'], $writed[$prod])) continue;
// Инициализация данных товара // Инициализация данных товара
$covr = null; $covr = null;
@ -122,7 +123,7 @@ use app\models\Search;
<div class="row p-2 rounded"> <div class="row p-2 rounded">
<img class="ml-0 rounded" src="<?= $covr ?>" /> <img class="ml-0 rounded" src="<?= $covr ?>" />
<div class="col ml-3 p-0 d-flex flex-column row_fixed_height"> <div class="col ml-3 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= $prod ?>/<?= $catn ?>"> <a class="my-auto text-dark" href="/product/<?= urlencode($prod) ?>/<?= urlencode($catn) ?>">
<h5 class="m-0"><b><?= $prod ?></b> <?= $catn ?></h5> <h5 class="m-0"><b><?= $prod ?></b> <?= $catn ?></h5>
<h6 class="m-0"><small><?= $name ?></small></h6> <h6 class="m-0"><small><?= $name ?></small></h6>
</a> </a>
@ -144,9 +145,7 @@ use app\models\Search;
<?php else : ?> <?php else : ?>
<?php if ($advanced ?? false) : ?> <?php if ($advanced ?? false) : ?>
<h1 class="m-auto gilroy text-center"><b>Ничего не найдено</b></h1> <h1 class="m-auto gilroy text-center"><b>Ничего не найдено</b></h1>
<?php else : ?> <?php else : ?>
<div class="row py-3 w-100"> <div class="row py-3 w-100">

View File

@ -44,9 +44,12 @@ if (isset($history) && $history) {
// Перебор найденных данных // Перебор найденных данных
$catn = $row['catn']; $catn = $row['catn'];
$prod = $row['prod'];
$_catn = urlencode($row['catn']);
$_prod = urlencode($row['prod']);
echo <<<HTML echo <<<HTML
<a class="dropdown-item button_white text-dark" href="/product/$catn">$catn</a> <a class="d-flex dropdown-item button_white text-dark" href="/product/$_prod/$_catn"><span class="col-auto pl-0">$catn</span><b class="col-auto ml-auto px-0 text-right">$prod</b></a>
HTML; HTML;
} }
} else { } else {

View File

@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
use app\models\Product;
use app\models\Account;
use app\models\Settings;
// Инициализация счетчика поставок
$i = $amount * ($page - 1);
// Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
?>
<?php if ($page > 1) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php foreach ($supplies ?? [] as $supply) : ?>
<?php
// Поиск аккаунта владельца
$account = '/account/' . Account::searchBySupplyId($supply->readId())['_key'];
if ($connect = $supply->searchConnectWithProduct()) {
// Найдена привязка поставки к аккаунту
$product = Product::searchById($connect->_to);
}
foreach ($supply->jrnl ?? [] as $jrnl) {
// Перебор записей в журнале
if ($jrnl['action'] === 'create') {
// Найдена дата создания
// Инициализация даты
$create = (new DateTime())->setTimestamp($jrnl['date'])->setTimezone(new DateTimeZone($timezone))->format('H:i d.m.Y') ?? 'Неизвестно';
// Выход из цикла
break;
}
}
?>
<div class="mb-3 row">
<div class="pr-0 col-auto"><?= ++$i ?>.</div>
<div class="pr-0 col-auto mr-auto overflow-hidden" title="Артикул">
<?= $supply->catn ?? 'Неизвестно' ?>
<span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span>
</div>
<div class="my-auto pr-0 col-auto" title="Стоимость">
<?= $supply->cost ?? '0' ?>р
</div>
<div class="my-auto pr-0 col-auto text-right" title="Дата создания">
<?= $create ?? 'Неизвестно' ?>
</div>
<?php if (empty($product)) : ?>
<a class="my-auto pr-0 col-auto fas fa-shopping-basket icon_red" title="Товар отсутствует" type="button" onclick="return profile_panel_input_suppliers_accounts_create_product(this, <?= "'$supply->catn'" ?? null ?>, <?= "'$supply->prod'" ?? null ?>);"></a>
<?php else : ?>
<a class="my-auto pr-0 col-auto fas fa-shopping-basket text-dark" title="Товар" href="<?= urlencode("/product/$product->prod/$product->catn") ?>"></a>
<?php endif ?>
<a class="my-auto pr-0 col-auto fas fa-user text-dark" title="Владелец" href="<?= urlencode($account ?? '/') ?>"></a>
<a class="my-auto col-auto fas fa-trash-alt text-dark" type="button" title="Удалить" onclick="page_profile_supplies_delete()"></a>
</div>
<?php if ($i < count($supplies) + $amount * ($page - 1)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>

View File

@ -0,0 +1,48 @@
section.hotline {
display: inline-flex;
}
section.hotline.vertical {
margin-bottom: unset;
margin-right: 10px;
height: unset;
flex-direction: column;
}
section.hotline.vertical:last-child {
margin-right: unset;
}
section.hotline:last-child {
margin-bottom: unset;
}
section.hotline > article {
margin-right: 3vh;
height: 100%;
display: flex;
border-radius: 3px;
}
section.hotline.vertical > article {
margin-right: unset;
margin-bottom: 3vh;
width: 20vh;
height: 10vw;
}
section.hotline:not(.vertical) > article:last-child {
margin-right: unset;
}
section.hotline.vertical > article:last-child {
margin-bottom: unset;
}
section.hotline > article > * {
margin: auto;
}
section.hotline>article>img {
pointer-events: none;
}

View File

@ -267,6 +267,10 @@ main {
border-top: 1px solid #CEDCFF !important; border-top: 1px solid #CEDCFF !important;
} }
.help-block-error {
color: #e60000;
}
/* @media (max-width: 400px) {} */ /* @media (max-width: 400px) {} */
/* Малые девайсы («ландшафтные телефоны», >= 576px) */ /* Малые девайсы («ландшафтные телефоны», >= 576px) */

View File

@ -1,5 +1,5 @@
#page_product nav>div, #page_product nav>div,
#page_product article>div { #page_product article>div:not(#products_list) {
background-color: #fff; background-color: #fff;
} }
@ -95,3 +95,20 @@
#page_product article .product_admin_menu { #page_product article .product_admin_menu {
background-color: #f4f4f6; background-color: #f4f4f6;
} }
#page_product .row_analog {
/* height : 65px; */
background-color: #fff;
border-right: 0 solid #fff;
transition: 100ms ease-in;
}
#page_product .row_fixed_height {
height: calc(65px - 1rem);
}
#page_product .row_analog>img {
object-fit: cover;
width: calc(65px - 1rem);
height: calc(65px - 1rem);
}

View File

@ -1,16 +0,0 @@
.ticker img {
object-fit: contain;
}
.bx-wrapper {
margin-bottom: 1.5rem !important;
width: 100vw;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.bx-wrapper .bx-viewport {
width: 100vw !important;
height: 2rem !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -43,6 +43,120 @@ function authentication(form, target = 'main') {
}); });
}; };
function restore(form, target = 'main') {
if (form == undefined) {
form = {
'_csrf': yii.getCsrfToken()
};
} else {
form = $(form).serializeArray();
}
form.push(
{
name: 'type',
value: 'restore'
},
{
name: 'target',
value: target
}
);
$.ajax({
url: '/restore',
type: 'post',
dataType: 'json',
data: form,
success: function (data, status, xhr) {
if (data !== undefined) {
// Получены данные с сервера
// Панель
if (typeof data.status === 'boolean') {
// Успешное выполнение
// Инициализация оболочки для уведомлений, оболочки для уведомлений и оболочки формы
let id, body, alert;
console.log(window.location.pathname);
console.log(window.location.pathname === '/authentication');
if (window.location.pathname === '/authentication') {
// Страница аутентификации
// Запись оболочки формы
body = document.getElementById('form_account');
// Запись оболочки для уведомлений
id = 'form_account_alert';
// Запись оболочки для уведомлений
alert = document.getElementById(id);
}
else {
// Панель аутентификации
// Запись оболочки формы
body = document.getElementById('form_account_panel');
// Запись идентификатора оболочки для уведомлений
id = 'form_account_panel_alert';
// Запись оболочки для уведомлений
alert = document.getElementById(id);
}
if (!(alert instanceof HTMLElement)) {
// Не найдена оболочка для уведомлений (подразумевается)
// Инициализация оболочки для уведомлений
const wrap = document.createElement('small');
wrap.classList.add('d-flex');
const element = document.createElement('small');
element.id = id;
element.classList.add('mx-auto');
// Запись в панель
wrap.appendChild(element);
body.appendChild(wrap);
// Реинициализация оболочки для уведомлений
alert = element;
}
if (data.status) {
// Отправлено сообщение
// Запись уведомления
alert.innerText = 'Сообщение отправлено на почту';
} else if (document.getElementById('accountform-mail').value === '') {
// Пустое содержимое почты
// Запись уведомления
alert.innerText = 'Введите почту';
// Удаление ненужных полей
document.getElementById('accountform-pswd').parentElement.remove();
panel.querySelector('[name="submitAuthentication"]').parentElement.remove();
panel.querySelector('[name="submitRegistration"]').remove();
} else {
// Не удалось отправить сообщение
// Запись уведомления
alert.innerText = 'Ошибка при отправке сообщения';
}
alert.innerText = data.status ? 'Сообщение отправлено на почту' : 'Введите почту';
}
}
account_response_success(data, status, xhr);
},
error: account_response_error
});
};
function deauthentication() { function deauthentication() {
$.ajax({ $.ajax({
url: '/deauthentication', url: '/deauthentication',

View File

@ -653,3 +653,5 @@ function cart_response_error(data, status, xhr) {
cart_response(data, status, xhr); cart_response(data, status, xhr);
}; };
document.dispatchEvent(new CustomEvent("cart.loaded"));

View File

@ -0,0 +1,679 @@
"use strict";
/**
* Бегущая строка
*
* @description
* Простой, но мощный класс для создания бегущих строк. Поддерживает
* перемещение мышью и прокрутку колесом, полностью настраивается очень гибок
* для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами.
* Имеет свой препроцессор, благодаря которому можно создавать бегущие строки
* без программирования - с помощью HTML-аттрибутов, а так же возможность
* изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать
* события при выбранных действиях для того, чтобы пользователь имел возможность
* дорабатывать функционал без изучения и изменения моего кода
*
* @example
* сonst hotline = new hotline();
* hotline.step = '-5';
* hotline.start();
*
* @todo
* 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты)
*
* @copyright WTFPL
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class hotline {
// Идентификатор
#id = 0;
// Оболочка (instanceof HTMLElement)
#shell = document.getElementById("hotline");
// Инстанция горячей строки
#instance = null;
// Перемещение
#transfer = true;
// Движение
#move = true;
// Наблюдатель
#observer = null;
// Наблюдатель
#block = new Set(["events"]);
// Настраиваемые параметры
transfer = null;
move = null;
delay = 10;
step = 1;
hover = true;
movable = true;
sticky = false;
wheel = false;
delta = null;
vertical = false;
observe = false;
events = new Map([
["start", false],
["stop", false],
["move", false],
["move.block", false],
["move.unblock", false],
["offset", false],
["transfer.start", true],
["transfer.end", true],
["onmousemove", false]
]);
constructor(id, shell) {
// Запись идентификатора
if (typeof id === "string" || typeof id === "number") this.#id = id;
// Запись оболочки
if (shell instanceof HTMLElement) this.#shell = shell;
}
start() {
if (this.#instance === null) {
// Нет запущенной инстанции бегущей строки
// Инициализация ссылки на ядро
const _this = this;
// Запуск движения
this.#instance = setInterval(function () {
if (_this.#shell.childElementCount > 1) {
// Найдено содержимое бегущей строки (2 и более)
// Инициализация буфера для временных данных
let buffer;
// Инициализация данных первого элемента в строке
const first = {
element: (buffer = _this.#shell.firstElementChild),
coords: buffer.getBoundingClientRect()
};
if (_this.vertical) {
// Вертикальная бегущая строка
// Инициализация сдвига у первого элемента (движение)
first.offset = isNaN(
(buffer = parseFloat(first.element.style.marginTop))
)
? 0
: buffer;
// Инициализация отступа до второго элемента у первого элемента (разделение)
first.separator = isNaN(
(buffer = parseFloat(
getComputedStyle(first.element).marginBottom
))
)
? 0
: buffer;
// Инициализация крайнего с конца ребра первого элемента в строке
first.end = first.coords.y + first.coords.height + first.separator;
} else {
// Горизонтальная бегущая строка
// Инициализация отступа у первого элемента (движение)
first.offset = isNaN(
(buffer = parseFloat(first.element.style.marginLeft))
)
? 0
: buffer;
// Инициализация отступа до второго элемента у первого элемента (разделение)
first.separator = isNaN(
(buffer = parseFloat(getComputedStyle(first.element).marginRight))
)
? 0
: buffer;
// Инициализация крайнего с конца ребра первого элемента в строке
first.end = first.coords.x + first.coords.width + first.separator;
}
if (
(_this.vertical &&
Math.round(first.end) < _this.#shell.offsetTop) ||
(!_this.vertical && Math.round(first.end) < _this.#shell.offsetLeft)
) {
// Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки)
if (
(_this.transfer === null && _this.#transfer) ||
_this.transfer === true
) {
// Перенос разрешен
if (_this.vertical) {
// Вертикальная бегущая строка
// Удаление отступов (движения)
first.element.style.marginTop = null;
} else {
// Горизонтальная бегущая строка
// Удаление отступов (движения)
first.element.style.marginLeft = null;
}
// Копирование первого элемента в конец строки
_this.#shell.appendChild(first.element);
if (_this.events.get("transfer.end")) {
// Запрошен вызов события: "перемещение в конец"
// Вызов события: "перемещение в конец"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.transfer.end`, {
detail: {
element: first.element,
offset: -(
(_this.vertical
? first.coords.height
: first.coords.width) + first.separator
)
}
})
);
}
}
} else if (
(_this.vertical &&
Math.round(first.coords.y) > _this.#shell.offsetTop) ||
(!_this.vertical &&
Math.round(first.coords.x) > _this.#shell.offsetLeft)
) {
// Передняя (движущая) граница первого элемента вышла из области видимости
if (
(_this.transfer === null && _this.#transfer) ||
_this.transfer === true
) {
// Перенос разрешен
// Инициализация отступа у последнего элемента (разделение)
const separator =
(buffer = isNaN(
(buffer = parseFloat(
getComputedStyle(_this.#shell.lastElementChild)[
_this.vertical ? "marginBottom" : "marginRight"
]
))
)
? 0
: buffer) === 0
? first.separator
: buffer;
// Инициализация координат первого элемента в строке
const coords = _this.#shell.lastElementChild.getBoundingClientRect();
if (_this.vertical) {
// Вертикальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginTop =
-coords.height - separator + "px";
} else {
// Горизонтальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginLeft =
-coords.width - separator + "px";
}
// Копирование последнего элемента в начало строки
_this.#shell.insertBefore(
_this.#shell.lastElementChild,
first.element
);
// Удаление отступов у второго элемента в строке (движения)
_this.#shell.children[1].style[
_this.vertical ? "marginTop" : "marginLeft"
] = null;
if (_this.events.get("transfer.start")) {
// Запрошен вызов события: "перемещение в начало"
// Вызов события: "перемещение в начало"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.transfer.start`, {
detail: {
element: _this.#shell.lastElementChild,
offset:
(_this.vertical ? coords.height : coords.width) +
separator
}
})
);
}
}
} else {
// Элемент в области видимости
if ((_this.move === null && _this.#move) || _this.move === true) {
// Движение разрешено
// Запись новых координат сдвига
const offset = first.offset + _this.step;
// Запись сдвига (движение)
_this.offset(offset);
if (_this.events.get("move")) {
// Запрошен вызов события: "движение"
// Вызов события: "движение"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move`, {
detail: {
from: first.offset,
to: offset
}
})
);
}
}
}
}
}, _this.delay);
if (this.hover) {
// Запрошена возможность останавливать бегущую строку
// Инициализация сдвига
let offset = 0;
// Инициализация слушателя события при перемещении элемента в бегущей строке
const listener = function (e) {
// Увеличение сдвига
offset += e.detail.offset ?? 0;
};
// Инициализация обработчика наведения курсора (остановка движения)
this.#shell.onmouseover = function (e) {
// Курсор наведён на бегущую строку
// Блокировка движения
_this.#move = false;
if (_this.events.get("move.block")) {
// Запрошен вызов события: "блокировка движения"
// Вызов события: "блокировка движения"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move.block`)
);
}
if (_this.movable) {
// Запрошена возможность двигать бегущую строку
_this.#shell.onmousedown = function (onmousedown) {
// Курсор активирован
// Инициализация слушателей события перемещения элемента в бегущей строке
document.addEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.addEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Инициализация буфера для временных данных
let buffer;
// Инициализация данных первого элемента в строке
const first = {
offset: isNaN(
(buffer = parseFloat(
_this.vertical
? _this.#shell.firstElementChild.style.marginTop
: _this.#shell.firstElementChild.style.marginLeft
))
)
? 0
: buffer
};
document.onmousemove = function (onmousemove) {
// Курсор движется
if (_this.vertical) {
// Вертикальная бегущая строка
// Инициализация буфера местоположения
const from = _this.#shell.firstElementChild.style.marginTop;
const to =
onmousemove.pageY -
(onmousedown.pageY + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginTop = to + "px";
if (_this.events.get("onmousemove")) {
// Запрошен вызов события: "перемещение мышью"
// Вызов события: "перемещение мышью"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
detail: { from, to }
})
);
}
} else {
// Горизонтальная бегущая строка
// Инициализация буфера местоположения
const from = _this.#shell.firstElementChild.style.marginLeft;
const to =
onmousemove.pageX -
(onmousedown.pageX + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginLeft = to + "px";
if (_this.events.get("onmousemove")) {
// Запрошен вызов события: "перемещение мышью"
// Вызов события: "перемещение мышью"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
detail: { from, to }
})
);
}
}
// Запись курсора
_this.#shell.style.cursor = "grabbing";
};
};
// Перещапись событий браузера (чтобы не дёргалось)
_this.#shell.ondragstart = null;
_this.#shell.onmouseup = function () {
// Курсор деактивирован
// Остановка обработки движения
document.onmousemove = null;
// Сброс сдвига
offset = 0;
document.removeEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.removeEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Восстановление курсора
_this.#shell.style.cursor = null;
};
}
};
// Инициализация обработчика отведения курсора (остановка движения)
this.#shell.onmouseleave = function (onmouseleave) {
// Курсор отведён от бегущей строки
if (!_this.sticky) {
// Отключено прилипание
// Остановка обработки движения
document.onmousemove = null;
document.removeEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.removeEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Восстановление курсора
_this.#shell.style.cursor = null;
}
// Сброс сдвига
offset = 0;
// Разблокировка движения
_this.#move = true;
if (_this.events.get("move.unblock")) {
// Запрошен вызов события: "разблокировка движения"
// Вызов события: "разблокировка движения"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move.unblock`)
);
}
};
}
if (this.wheel) {
// Запрошена возможность прокручивать колесом мыши
// Инициализация обработчика наведения курсора (остановка движения)
this.#shell.onwheel = function (e) {
// Курсор наведён на бегущую
// Инициализация буфера для временных данных
let buffer;
// Перемещение
_this.offset(
(isNaN(
(buffer = parseFloat(
_this.#shell.firstElementChild.style[
_this.vertical ? "marginTop" : "marginLeft"
]
))
)
? 0
: buffer) +
(_this.delta === null
? e.wheelDelta
: e.wheelDelta > 0
? _this.delta
: -_this.delta)
);
};
}
}
if (this.observe) {
// Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки
if (this.#observer === null) {
// Отсутствует наблюдатель
// Инициализация ссылки на ядро
const _this = this;
// Инициализация наблюдателя
this.#observer = new MutationObserver(function (mutations) {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
// Запись параметра в инстанцию бегущей строки
_this.configure(mutation.attributeName);
}
}
// Перезапуск бегущей строки
_this.restart();
});
// Активация наблюдения
this.#observer.observe(this.#shell, {
attributes: true
});
}
} else if (this.#observer instanceof MutationObserver) {
// Запрошено отключение наблюдения
// Деактивация наблюдения
this.#observer.disconnect();
// Удаление наблюдателя
this.#observer = null;
}
if (this.events.get("start")) {
// Запрошен вызов события: "запуск"
// Вызов события: "запуск"
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.start`));
}
return this;
}
stop() {
// Остановка бегущей строки
clearInterval(this.#instance);
// Удаление инстанции интервала
this.#instance = null;
if (this.events.get("stop")) {
// Запрошен вызов события: "остановка"
// Вызов события: "остановка"
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`));
}
return this;
}
restart() {
// Остановка бегущей строки
this.stop();
// Запуск бегущей строки
this.start();
}
configure(attribute) {
// Инициализация названия параметра
const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
if (typeof parameter === "string") {
// Параметр найден
// Проверка на разрешение изменения
if (this.#block.has(parameter)) return;
// Инициализация значения параметра
const value = this.#shell.getAttribute(attribute);
// Инициализация буфера для временных данных
let buffer;
// Запись параметра
this[parameter] = isNaN((buffer = parseFloat(value)))
? value === "true"
? true
: value === "false"
? false
: value
: buffer;
}
return this;
}
offset(value) {
// Запись отступа
this.#shell.firstElementChild.style[
this.vertical ? "marginTop" : "marginLeft"
] = value + "px";
if (this.events.get("offset")) {
// Запрошен вызов события: "сдвиг"
// Вызов события: "сдвиг"
document.dispatchEvent(
new CustomEvent(`hotline.${this.#id}.offset`, {
detail: {
to: value
}
})
);
}
return this;
}
static preprocessing(event = false) {
// Инициализация счётчиков инстанций горячей строки
const success = new Set();
let error = 0;
for (const element of document.querySelectorAll('*[data-hotline="true"]')) {
// Перебор элементов для инициализации бегущих строк
if (typeof element.id === "string") {
// Найден идентификатор
// Инициализация инстанции бегущей строки
const hotline = new this(element.id, element);
for (const attribute of element.getAttributeNames()) {
// Перебор аттрибутов
// Запись параметра в инстанцию бегущей строки
hotline.configure(attribute);
}
// Запуск бегущей строки
hotline.start();
// Запись инстанции бегущей строки в элемент
element.hotline = hotline;
// Запись в счётчик успешных инициализаций
success.add(hotline);
} else ++error;
}
if (event) {
// Запрошен вызов события: "предварительная подготовка"
// Вызов события: "предварительная подготовка"
document.dispatchEvent(
new CustomEvent(`hotline.preprocessed`, {
detail: {
success,
error
}
})
);
}
}
}
document.dispatchEvent(
new CustomEvent("hotline.loaded", {
detail: { hotline }
})
);

View File

@ -0,0 +1,2 @@
/*! js-cookie v3.0.1 | MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}return function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})}));

View File

@ -0,0 +1,33 @@
function generate(id, text = 'Загрузка') {
// Поиск файла со стилем индикатора загрузки
const file = document.querySelector('[href="/css/loading.css"]');
if (file === null) {
// Не загружен файл
// Запись ссылки на файл
let link = document.createElement("link");
link.href = '/css/loading.css';
link.rel = "stylesheet";
document.getElementsByTagName("head")[0].appendChild(link);
}
if (typeof id === 'string', typeof text === 'string') {
// Получены необходимые параметры
// Инициализация оболочки
let wrap = document.createElement("p");
wrap.id = 'loading_' + id;
wrap.classList.add('my-2', 'd-flex', 'justify-content-center');
// Инициализация иконки
let icon = document.createElement('i');
icon.classList.add('loading', 'mr-2');
// Инициализация архитектуры
wrap.appendChild(icon);
wrap.append(text);
return wrap
}
}

View File

@ -12,11 +12,6 @@ function main_response(data, status, xhr) {
// Обновление документа // Обновление документа
main.innerHTML = data.main; main.innerHTML = data.main;
// alert('Основной блок на ' + data.main);
// Реинициализация
reinitialization(main);
}; };
// Заголовок // Заголовок

View File

@ -25,6 +25,71 @@ function order_init(order) {
return false; return false;
}; };
/**
* Инициализация списка
*
* @param {number} amount
* @param {number} page
* @returns
*/
function order_init_list(amount, page, id = 'orders_panel_moderation_list') {
if (typeof amount === 'number' && typeof page === 'number' && typeof id === 'string') {
$.ajax({
url: `/orders/list/${page}`,
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
amount
},
success: (data, status, xhr) => {
// Обработка ответа
if (data !== undefined) {
// Получены данные с сервера
if (data.list !== undefined && data.list !== null) {
// Найдены данные поставщиков
// Запись cookie с номером страницы
Cookies.set('order_init_list', parseInt(Cookies.get('order_init_list') ?? 1) + 1, { path: window.location.pathname });
// Инициализация оболочки
let wrap = document.getElementById(id);
// Запись в документ
wrap.innerHTML += data.list;
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (wrap.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) order_init_list(amount, parseInt(Cookies.get('order_init_list')), id);
// Блокировка
block = true;
}
};
}
}
// Переход к остальным обработчикам
orders_response_success(data, status, xhr);
},
error: orders_response_error
});
return false;
};
return true;
}
function order_accept(order) { function order_accept(order) {
$.ajax({ $.ajax({
@ -591,3 +656,9 @@ function orders_supply_status_edit(order, prod, catn, delivery, element) {
return false; return false;
}; };
// Запись (сброс) cookie с номером страницы
Cookies.set('order_init_list', 1, { path: window.location.pathname });
// Вызов события: "loaded"
document.dispatchEvent(new CustomEvent("orders_panel.loaded"));

View File

@ -21,6 +21,72 @@ function product_image_choose(key, wrap) {
}; };
}; };
/**
* Инициализация
*
* @param {number} amount
* @param {number} page
* @returns
*/
function panel_analogs_initializing(prod, catn, amount, page, id = 'products_list') {
if (typeof prod === 'string' && typeof catn === 'string' && typeof amount === 'number' && typeof page === 'number'&& typeof id === 'string') {
$.ajax({
url: `/product/${prod}/${catn}/analogs/${page}`,
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
amount
},
success: (data, status, xhr) => {
// Обработка ответа
if (data !== undefined) {
// Получены данные с сервера
if (data.list !== undefined && data.list !== null) {
// Найдены данные поставщиков
// Запись cookie с номером страницы
Cookies.set('product_analogs_page', parseInt(Cookies.get('product_analogs_page') ?? 1) + 1, { path: window.location.pathname });
// Инициализация оболочки
let wrap = document.getElementById(id);
// Запись в документ
wrap.innerHTML += data.list;
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (wrap.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) panel_analogs_initializing(prod, catn, amount, parseInt(Cookies.get('product_analogs_page')), id);
// Блокировка
block = true;
}
};
}
}
// Переход к остальным обработчикам
product_response_success(data, status, xhr);
},
error: product_response_error
});
return false;
};
return true;
}
function product_response(data, status, xhr) { function product_response(data, status, xhr) {
// Обработка ответов // Обработка ответов
@ -41,3 +107,6 @@ function product_response_error(data, status, xhr) {
product_response(data, status, xhr); product_response(data, status, xhr);
}; };
// Запись (сброс) cookie с номером страницы
Cookies.set('product_analogs_page', 1, { path: window.location.pathname });

View File

@ -388,27 +388,46 @@ function product_panel_description_save(catn, prod, element) {
* @param {*} catn * @param {*} catn
* @returns * @returns
*/ */
function product_panel_connect(catn, prod, group) { function product_panel_connect(catn, prod, target_catn, target_prod, list) {
if (group === null || group === '') { if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
return false;
}
if (group === undefined) { if (target_catn === undefined) {
// Не получены данные о группе // Не получены данные о группе
group = prompt('Подключить аналог'); target_catn = prompt('Артикул');
}
if (target_prod === undefined) {
// Не получены данные о группе
target_prod = prompt('Производитель');
} }
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
$.ajax({ $.ajax({
url: '/product/' + prod + '/' + catn + '/connect', url: '/product/' + prod + '/' + catn + '/connect',
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: { data: {
'_csrf': yii.getCsrfToken(), '_csrf': yii.getCsrfToken(),
'catn': group 'catn': target_catn,
'prod': target_prod
}, },
success: product_response_success, success: (data, status, xhr) => {
if (data.list !== undefined && data.list !== null) {
// Найдены данные поставщиков
// Запись cookie с номером страницы
Cookies.set('product_analogs_page', 1, { path: window.location.pathname });
// Инициализация оболочки
let wrap = document.getElementById(list);
// Запись в документ
wrap.innerHTML = data.list;
// Переход к остальным обработчикам
product_response_success(data, status, xhr);
}},
error: product_response_error error: product_response_error
}); });
@ -418,24 +437,35 @@ function product_panel_connect(catn, prod, group) {
return true; return true;
} }
/** /**
* Отключение от группы * Отключение от группы
* *
* @param {*} catn * @param {*} catn
* @returns * @returns
*/ */
function product_panel_disconnect(catn, prod) { function product_panel_disconnect(catn, prod, element) {
if (catn !== null && catn !== undefined) { if (typeof prod === 'string' && typeof catn === 'string' && typeof element === 'object') {
$.ajax({ $.ajax({
url: '/product/' + prod + '/' + catn + '/disconnect', url: '/product/' + prod + '/' + catn + '/disconnect',
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: { data: {
'_csrf': yii.getCsrfToken(), '_csrf': yii.getCsrfToken()
'catn': prompt('Отсоединить аналог (пустое поле - все аналоги)') },
success: function (data, status, xhr) {
if (data !== undefined) {
// Получены данные с сервера
if (data.disconnected === 1) {
// Статус об отключении
// Удаление элемента
element.remove();
};
};
product_response_success(data, status, xhr);
}, },
success: product_response_success,
error: product_response_error error: product_response_error
}); });
@ -443,10 +473,10 @@ function product_panel_disconnect(catn, prod) {
}; };
return true; return true;
} }
function product_panel_delete(catn, prod) { function product_panel_delete(catn, prod) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) { if (typeof prod === 'string' && typeof catn === 'string') {
$.ajax({ $.ajax({
url: '/product/' + prod + '/' + catn + '/delete', url: '/product/' + prod + '/' + catn + '/delete',
type: 'post', type: 'post',

Some files were not shown because too many files have changed in this diff Show More