крупное обновление всего

This commit is contained in:
root 2022-10-29 00:20:33 +00:00
parent 4fafe85639
commit fc38285d06
97 changed files with 3096 additions and 733 deletions

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

@ -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,9 @@ class ProductController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
// Запись в буфер возврата
$return['list'] = $this->renderPartial('analogs', ['model' => $from]);
return $return; return $return;
} }
@ -462,7 +499,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 +508,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 +523,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 +533,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 +563,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

@ -884,7 +884,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-запрос
@ -892,11 +892,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) {
// Перебор заявок // Перебор заявок
@ -1059,6 +1063,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()) {
// Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА // Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА
@ -1068,7 +1074,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()) {
// Удалено ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВКИ // Удалено ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВКИ
@ -1077,7 +1083,7 @@ class ProfileController extends Controller
// Удалена инстанция поставки // Удалена инстанция поставки
// Отправка уведомления // Отправка уведомления
Notification::_write("Инстанция поставки $_key была удалена", account: $account->_key); Notification::_write("Инстанция поставки $_key была удалена", account: $account->readId());
} }
} }
} }
@ -1085,7 +1091,7 @@ class ProfileController extends Controller
// Не найдена инстанция поставки // Не найдена инстанция поставки
// Отправка уведомления // Отправка уведомления
Notification::_write("Не найдена инстанция поставки $_key", account: $account->_key); Notification::_write("Не найдена инстанция поставки $_key", account: $account->readId());
} }
// Запись в буфер вывода реинициализированного элемента // Запись в буфер вывода реинициализированного элемента

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()) {
@ -425,9 +429,85 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (!mkdir($path, 0775, true)) if (!mkdir($path, 0775, true))
throw new Exception('Не удалось создать директорию', 500); throw new Exception('Не удалось создать директорию', 500);
$this->file_excel->saveAs($path = "$path/" . $filename = $this->file_excel->baseName . '.' . $this->file_excel->extension); $this->file_excel->saveAs($path = "$path/" . $this->file_excel->baseName . '.' . $this->file_excel->extension);
$data[] = Excel::import($path, [ // Инициализация инстанции файла
$file = new File();
// Запись настроек файла
$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($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 +521,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 +569,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);
@ -660,6 +741,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 +756,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 +773,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
// } // }
// Добавление в группу аналогов // Добавление в группу аналогов
$group->writeProduct($product); // $group->writeProduct($product);
} }
return !$error; return !$error;
@ -698,12 +782,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 +796,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 +817,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 +1048,55 @@ class Supply extends Product implements ProductInterface, OfferInterface
} }
/** /**
* Вызывается после загрузки поставок из excel-документа * Вызывается после загрузки excel-документа на сервер
* *
* @param string|int $warehouse Склад
*
* @return bool Статус выполнения
*/
public static function afterImportExcel(string|int $warehouse = 'неизвестен'): bool
{
// Отправка уведомления о загрузке
$save = Notification::_write("Загружены товары для склада \"$warehouse\"");
// Инициализация периода
$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");
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 +1106,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

@ -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

@ -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/<?= $prod ?>/<?= $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>
@ -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);">Подключить аналог</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) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<?php <?php
@ -34,7 +38,7 @@ $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/<?= $product->prod ?? 'Неизвестно' ?>/<?= $product->catn ?? 'Неизвестно' ?>">
<?= $product->catn ?? 'Неизвестно' ?> <?= $product->catn ?? 'Неизвестно' ?>
<span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span> <span class="text-dark" title="Производитель"> (<?= $product->prod ?? 'Неизвестно' ?>)</span>
@ -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

@ -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;
@ -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,10 @@ if (isset($history) && $history) {
// Перебор найденных данных // Перебор найденных данных
$catn = $row['catn']; $catn = $row['catn'];
$prod = $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="<?= "/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 ($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

@ -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,96 @@ 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') {
// Успешное выполнение
// Инициализация панели
const panel = document.getElementById('form_account_panel');
// Инициализация оболочки для уведомлений
let alert = document.getElementById('form_account_panel_alert');
if (!(alert instanceof HTMLElement)) {
// Не найдена оболочка для уведомлений (подразумевается)
// Инициализация оболочки для уведомлений
const wrap = document.createElement('small');
wrap.classList.add('d-flex');
const element = document.createElement('small');
element.id = 'form_account_panel_alert';
element.classList.add('mx-auto');
// Запись в панель
wrap.appendChild(element);
panel.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,25 +388,29 @@ 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) {
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: product_response_success,
error: product_response_error error: product_response_error
@ -418,24 +422,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
}); });
@ -446,7 +461,7 @@ function product_panel_disconnect(catn, prod) {
} }
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',

View File

@ -393,3 +393,5 @@ function page_profile_response_error(data, status, xhr) {
page_profile_response(data, status, xhr); page_profile_response(data, status, xhr);
}; };
document.dispatchEvent(new CustomEvent("profile.loaded"));

View File

@ -72,32 +72,44 @@ function page_profile_panel_choose(button = 'profile_panel_input_accounts') {
document.querySelector('[for="profile_panel_input_notifications"]').classList.remove('active'); document.querySelector('[for="profile_panel_input_notifications"]').classList.remove('active');
document.querySelector('[for="profile_panel_input_settings"]').classList.remove('active'); document.querySelector('[for="profile_panel_input_settings"]').classList.remove('active');
if (button === 'profile_panel_input_products') {
// Открытие вкладки с товарами
profile_panel_products_read('@last', 'all', '@last', '@last', 1, true);
}
if (button === 'profile_panel_input_supplies') {
// Открытие вкладки с поставками
profile_panel_supplies_read(1, true);
}
if (button === 'profile_panel_input_accounts') {
// Инициализация подвкладки по умолчанию для вкладки "аккаунты"
page_profile_panel_accounts_choose(undefined, true);
} else if (button === 'profile_panel_input_accounts_control') {
// Инициализация подвкладки "пользователи" для вкладки "аккаунты"
page_profile_panel_accounts_choose('profile_panel_input_accounts_control', true);
button = 'profile_panel_input_accounts';
} else if (button === 'profile_panel_input_accounts_suppliers') {
// Инициализация подвкладки "поставщики" для вкладки "аккаунты"
page_profile_panel_accounts_choose('profile_panel_input_accounts_suppliers', true);
button = 'profile_panel_input_accounts';
} else {
// Деиницилизация подкладок для вкладки "аккаунты"
page_profile_panel_accounts_choose('disable', true);
}
// Активация запрошенной вкладки // Активация запрошенной вкладки
document.getElementById(button).checked = true; document.getElementById(button).checked = true;
document.getElementById(button).removeAttribute('onclick'); document.getElementById(button).removeAttribute('onclick');
document.querySelector('[for="' + button + '"]').classList.add('active'); document.querySelector('[for="' + button + '"]').classList.add('active');
if (button === 'profile_panel_input_products') {
// Открытие вкладки с товарами
profile_panel_products_read('@last', 'all');
}
if (button === 'disable') {
// Инициализация активной подвкладки вкладки "Аккаунты"
page_profile_panel_accounts_choose('disable', true);
return;
} else if (button === 'profile_panel_input_accounts') {
// Инициализация активной подвкладки вкладки "Аккаунты"
page_profile_panel_accounts_choose(undefined, true);
} else if (button !== 'profile_panel_input_accounts') {
// Инициализация активной подвкладки вкладки "Аккаунты"
page_profile_panel_accounts_choose('disable', true);
}
} }
function page_profile_panel_accounts_choose(button = 'profile_panel_input_accounts_control', active = false) { function page_profile_panel_accounts_choose(button = 'profile_panel_input_accounts_control', active = false) {
@ -112,17 +124,25 @@ function page_profile_panel_accounts_choose(button = 'profile_panel_input_accoun
document.getElementById('profile_panel_input_accounts_suppliers').checked = document.getElementById('profile_panel_input_accounts_suppliers').checked =
document.getElementById('profile_panel_input_accounts_control').checked = false document.getElementById('profile_panel_input_accounts_control').checked = false
document.getElementById('profile_panel_input_accounts_suppliers').setAttribute('onclick', 'return page_profile_panel_accounts_choose(\'profile_panel_input_accounts_suppliers\');'); document.getElementById('profile_panel_input_accounts_suppliers').setAttribute('onclick', 'return page_profile_panel_choose(\'profile_panel_input_accounts_suppliers\');');
document.getElementById('profile_panel_input_accounts_control').setAttribute('onclick', 'return page_profile_panel_accounts_choose(\'profile_panel_input_accounts_control\');'); document.getElementById('profile_panel_input_accounts_control').setAttribute('onclick', 'return page_profile_panel_choose(\'profile_panel_input_accounts_control\');');
document.querySelector('[for="profile_panel_input_accounts_suppliers"]').classList.remove('active'); document.querySelector('[for="profile_panel_input_accounts_suppliers"]').classList.remove('active');
document.querySelector('[for="profile_panel_input_accounts_control"]').classList.remove('active'); document.querySelector('[for="profile_panel_input_accounts_control"]').classList.remove('active');
if (button === 'disable') { if (button === 'disable') {
return; return;
} else if (button === 'profile_panel_input_accounts_control') {
// Блок: "пользователи"
// Генерация списка
profile_panel_accounts_read(1, true);
} else if (button === 'profile_panel_input_accounts_suppliers') { } else if (button === 'profile_panel_input_accounts_suppliers') {
// Инициализация содержимого блока "Заявки на регистрацию поставщиков" // Блок: "поставщики"
page_profile_panel_input_suppliers_requests_init();
// Генерация списка
page_profile_panel_input_suppliers_requests_init(1);
} }
// Инициализация запрошенной вкладки // Инициализация запрошенной вкладки
@ -131,14 +151,14 @@ function page_profile_panel_accounts_choose(button = 'profile_panel_input_accoun
document.querySelector('[for="' + button + '"]').classList.add('active'); document.querySelector('[for="' + button + '"]').classList.add('active');
} }
function page_profile_panel_input_suppliers_requests_init(wrap = 'profile_panel_input_suppliers_requests') { function page_profile_panel_input_suppliers_requests_init(page = 1, id = 'profile_panel_input_suppliers_requests') {
// Инициализация содержимого блока "Заявки на регистрацию поставщиков" // Инициализация содержимого блока "Заявки на регистрацию поставщиков"
// Инициализация оболочки // Инициализация оболочки
wrap = document.getElementById(wrap); const wrap = document.getElementById(id);
$.ajax({ $.ajax({
url: '/profile/panel/suppliers/requests/search', url: '/profile/panel/suppliers/requests/search/' + page,
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: { data: {
@ -153,6 +173,9 @@ function page_profile_panel_input_suppliers_requests_init(wrap = 'profile_panel_
if (data.suppliers !== undefined && data.suppliers !== null) { if (data.suppliers !== undefined && data.suppliers !== null) {
// Найдены данные поставщиков // Найдены данные поставщиков
// Запись cookie с номером страницы
Cookies.set('page_profile_panel_input_suppliers_requests_init_page', parseInt(Cookies.get('page_profile_panel_input_suppliers_requests_init_page') ?? 1) + 1, { path: window.location.pathname });
// Удаление данных в оболочке // Удаление данных в оболочке
wrap.innerHTML = null; wrap.innerHTML = null;
@ -162,6 +185,26 @@ function page_profile_panel_input_suppliers_requests_init(wrap = 'profile_panel_
// Запись в документ // Запись в документ
wrap.appendChild(html); wrap.appendChild(html);
} }
// Инициализация оболочки
let element = document.getElementById(id);
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (element.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) page_profile_panel_input_suppliers_requests_init(parseInt(Cookies.get('page_profile_panel_input_suppliers_requests_init_page')), id);
// Блокировка
block = true;
}
};
} }
} }
@ -738,7 +781,7 @@ function profile_panel_input_suppliers_accounts_create_product(button, catn, pro
} }
// Прочитать товары // Прочитать товары
function profile_panel_products_read(search = '@last', type = '@last', from = '@last', to = '@last') { function profile_panel_products_read(search = '@last', type = '@last', from = '@last', to = '@last', page = 1, purge = false) {
// '@last' - оставить без изменений и взять данные из cookie // '@last' - оставить без изменений и взять данные из cookie
if (search.length > 1) { if (search.length > 1) {
@ -754,19 +797,52 @@ function profile_panel_products_read(search = '@last', type = '@last', from = '@
// Запрос // Запрос
$.ajax({ $.ajax({
url: '/products/read/' + type, url: '/products/read/' + type + '/' + page,
type: 'post', type: 'post',
dataType: 'json', dataType: 'json',
data: data, data: data,
success: (data, status, xhr) => { success: (data, status, xhr) => {
if (data.products !== undefined) {
// Передан список
// Инициализация оболочки // Инициализация оболочки
let element = document.getElementById('profile_panel_input_products_wrap'); let element = document.getElementById('profile_panel_input_products_wrap');
if (data.products !== undefined && element !== null) { if (element !== null) {
// Передан список продуктов и найден элемент-оболочка продуктов // Найден элемент-оболочка
// Запись cookie с номером страницы
Cookies.set('profile_panel_products_read_page', parseInt(Cookies.get('profile_panel_products_read_page') ?? 1) + 1, { path: window.location.pathname });
if (purge || element.children.length === 0) {
// Не найдены строки, либо запрошена очистка
// Запись полученного содержимого // Запись полученного содержимого
element.innerHTML = data.products; element.innerHTML = data.products;
} else {
// Найдены строки (подразумевается, что здесь могут быть только они)
// Запись полученного содержимого
element.innerHTML += data.products;
}
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (element.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) profile_panel_products_read('@last', 'all', '@last', '@last', parseInt(Cookies.get('profile_panel_products_read_page')));
// Блокировка
block = true;
}
};
}
} }
return page_profile_response_success(data, status, xhr); return page_profile_response_success(data, status, xhr);
@ -777,3 +853,137 @@ function profile_panel_products_read(search = '@last', type = '@last', from = '@
return false; return false;
} }
// Прочитать поставки
function profile_panel_supplies_read(page = 1, purge = false) {
// Запрос
$.ajax({
url: '/supplies/read/' + page,
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken()
},
success: (data, status, xhr) => {
if (data.supplies !== undefined) {
// Передан список
// Инициализация оболочки
let element = document.getElementById('profile_panel_input_supplies_wrap');
if (element !== null) {
// Найден элемент-оболочка
// Запись cookie с номером страницы
Cookies.set('profile_panel_supplies_read_page', parseInt(Cookies.get('profile_panel_supplies_read_page') ?? 1) + 1, { path: window.location.pathname });
if (purge || element.children.length === 0) {
// Не найдены строки, либо запрошена очистка
// Запись полученного содержимого
element.innerHTML = data.supplies;
} else {
// Найдены строки (подразумевается, что здесь могут быть только они)
// Запись полученного содержимого
element.innerHTML += data.supplies;
}
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (element.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) profile_panel_supplies_read(parseInt(Cookies.get('profile_panel_supplies_read_page')));
// Блокировка
block = true;
}
};
}
}
return page_profile_response_success(data, status, xhr);
},
error: page_profile_response_error
});
return false;
}
// Прочитать аккаунты
function profile_panel_accounts_read(page = 1, purge = false) {
// Запрос
$.ajax({
url: '/accounts/read/' + page,
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken()
},
success: (data, status, xhr) => {
if (data.accounts !== undefined) {
// Передан список
// Инициализация оболочки
let element = document.getElementById('profile_panel_input_accounts_list');
if (element !== null) {
// Найден элемент-оболочка
// Запись cookie с номером страницы
Cookies.set('profile_panel_accounts_read_page', parseInt(Cookies.get('profile_panel_accounts_read_page') ?? 1) + 1, { path: window.location.pathname });
if (purge || element.children.length === 0) {
// Не найдены строки, либо запрошена очистка
// Запись полученного содержимого
element.innerHTML = data.accounts;
} else {
// Найдены строки (подразумевается, что здесь могут быть только они)
// Запись полученного содержимого
element.innerHTML += data.accounts;
}
// Инициализация блокировщика
let block = false;
document.onscroll = function (e) {
// Обработчик события: "прокрутка экрана"
if (element.lastElementChild.getBoundingClientRect().y <= window.innerHeight) {
// Строка в области видимости окна
// Генерация и запись следующей страницы
if (!block) profile_panel_accounts_read(parseInt(Cookies.get('profile_panel_accounts_read_page')));
// Блокировка
block = true;
}
};
}
}
return page_profile_response_success(data, status, xhr);
},
error: page_profile_response_error
});
return false;
}
// Запись (сброс) cookie с номером страницы
Cookies.set('page_profile_panel_input_suppliers_requests_init_page', 1, { path: '/profile/panel' });
Cookies.set('profile_panel_products_read_page', 1, { path: '/profile/panel' });
Cookies.set('profile_panel_supplies_read_page', 1, { path: '/profile/panel' });
Cookies.set('profile_panel_accounts_read_page', 1, { path: '/profile/panel' });
document.dispatchEvent(new CustomEvent("profile_panel.loaded"));

View File

@ -34,3 +34,12 @@ function reinitialization(target) {
return false; return false;
}; };
new MutationObserver(function () {
reinitialization(document.getElementsByTagName('main')[0]);
}).observe(
document.getElementsByTagName('main')[0],
{
childList: true,
}
);

View File

@ -27,7 +27,7 @@ function product_search(text = '', advanced = 0) {
}; };
}; };
function product_search_history(text = '', advanced = 0) { function product_search_history() {
$.ajax({ $.ajax({
url: '/search', url: '/search',
type: 'post', type: 'post',
@ -38,10 +38,10 @@ function product_search_history(text = '', advanced = 0) {
'history': true 'history': true
}, },
success: function (data, status, xhr) { success: function (data, status, xhr) {
search_panel_success(text, advanced, data, status, xhr); search_panel_success('', 0, data, status, xhr);
}, },
error: function (data, status, xhr) { error: function (data, status, xhr) {
search_panel_error(text, advanced, data, status, xhr); search_panel_error('', 0, data, status, xhr);
}, },
statusCode: search_panel_statusCode statusCode: search_panel_statusCode
}); });
@ -68,36 +68,6 @@ function search_panel_hide(line = 'search_line', panel = 'search_line_window') {
}; };
function search_panel_response(text, advanced, data, status, xhr) { function search_panel_response(text, advanced, data, status, xhr) {
if (data !== undefined) {
// Получены данные с сервера
// Окно поиска
if (data.panel !== undefined) {
panel = document.getElementById('search_line_window');
// Обновление окна результатов поиска
panel.innerHTML = data.panel;
// Реинициализация
reinitialization(panel);
};
// Таймер для повтора запроса
if (data.timer !== undefined) {
// Ожидание перед повторным запросом
if (getCookie('search') !== 'processing') {
// На данный момент нет других запросов поиска
// Запись о существовании запроса
search_panel_statusCode_progress();
// Запрос
setTimeout(product_search, data.timer + '000', text, advanced);
};
};
// Отображение окна поиска // Отображение окна поиска
if (data.search_line_window_show === 1) { if (data.search_line_window_show === 1) {
search_panel_show(); search_panel_show();
@ -107,6 +77,75 @@ function search_panel_response(text, advanced, data, status, xhr) {
if (data.search_line_window_hide === 1) { if (data.search_line_window_hide === 1) {
search_panel_hide(); search_panel_hide();
}; };
// Таймер для повтора запроса
if (data.timer !== undefined) {
// Ожидание перед повторным запросом
if (getCookie('search') === 'awaiting') {
// На данный момент нет других запросов поиска
if (data.panel === undefined) {
// Запись о существовании запроса
search_panel_statusCode_progress(setInterval(product_search, data.timer + '000', text, advanced));
} else {
// Удаление цикла
clearInterval(getCookie('search'));
// Запись о начале ожидания
search_panel_statusCode_awaiting();
}
}
} else {
// Удаление цикла
clearInterval(getCookie('search'));
// Запись о начале ожидания
search_panel_statusCode_awaiting();
}
if (data !== undefined) {
// Получены данные с сервера
// Окно поиска
if (data.panel !== undefined) {
// Получены данные для окна поиска
if (getCookie('search') !== 'awaiting') {
// Удаление цикла
clearInterval(getCookie('search'));
}
// Инициализация панели
let panel = document.getElementById('search_line_window');
// Обновление окна результатов поиска
panel.innerHTML = data.panel;
// Инициализация индикатора загрузки
let icon = document.getElementById('loading_search');
// Удаление индикатора загрузки
if (icon !== null) icon.remove();
// Реинициализация
reinitialization(panel);
} else {
// Не получены данные для окна поиска
if (document.getElementById('loading_search') === null) {
// Не найден индикатор загрузки поиска
// Инициализация панели
let panel = document.getElementById('search_line_window');
// Очистка
panel.innerHTML = null;
// Запись индикатора загрузки
panel.appendChild(generate('search', 'Поиск'));
}
}
}; };
main_response(data, status, xhr); main_response(data, status, xhr);
@ -129,19 +168,21 @@ function search_panel_error(text, advanced, data, status, xhr) {
function search_panel_statusCode() { function search_panel_statusCode() {
return { return {
200: search_panel_statusCode_waiting, 200: search_panel_statusCode_awaiting,
404: search_panel_statusCode_waiting, 404: search_panel_statusCode_awaiting,
}; };
}; };
function search_panel_statusCode_waiting() { function search_panel_statusCode_awaiting() {
// Ожидание нового запроса // Ожидание нового запроса
document.cookie = "search=waiting; path=/"; document.cookie = "search=awaiting; path=/";
document.body.style.cursor = "unset"; document.body.style.cursor = "unset";
}; };
function search_panel_statusCode_progress() { search_panel_statusCode_awaiting();
function search_panel_statusCode_progress(id = 0) {
// Выполнение запроса // Выполнение запроса
document.cookie = "search=processing; path=/"; document.cookie = "search=" + id + "; path=/";
document.body.style.cursor = "progress"; document.body.style.cursor = "progress";
}; };

View File

@ -38,3 +38,5 @@ function initTextarea(textarea_id, counter_current_id, counter_maximum_id, disab
}; };
}); });
}; };
document.dispatchEvent(new CustomEvent("textarea.loaded"));