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

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 app\models\Account;
use app\models\AccountForm;
class AccountController extends Controller
{
@ -24,7 +25,9 @@ class AccountController extends Controller
'allow' => true,
'actions' => [
'file',
'data'
'data',
'restore',
'generate-password'
]
],
[
@ -38,7 +41,10 @@ class AccountController extends Controller
],
[
'allow' => true,
'actions' => ['accept', 'decline'],
'actions' => [
'read',
'accept',
'decline'],
'matchCallback' => function ($rule, $action): bool {
if (
!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\Order;
use app\models\Notification;
use yii;
use yii\web\Controller;
use yii\web\Response;
@ -61,6 +62,9 @@ class AuthenticationController extends Controller
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_panel = $this->renderPartial('/notification/panel', ['notifications_panel_full' => true]);

View File

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

View File

@ -56,6 +56,21 @@ class OrderController extends Controller
'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,
'roles' => ['?'],
@ -283,51 +298,20 @@ class OrderController extends Controller
to: $to,
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') {
// Обработка панели модерации заказов
// Инициализация заказов
$data = Order::searchSmart(
stts: '@all',
stts: $stts,
limit: 10,
page: 1,
supplies: true
supplies: true,
from: $from,
to: $to,
search: $search
);
// Инициализация панели модератора заказов
if (!yii::$app->user->isGuest && Account::isMinimalAuthorized($account)) {
// Имеет доступ пользователь
// Инициализация заказов для модератора
$moderator_data = Order::searchSmart(
account: '@all',
stts: $stts,
limit: 10,
page: 1,
supplies: true,
from: $from,
to: $to,
search: $search
);
} else {
// Не имеет доступ пользователь
// Инициализация заглушки
$moderator_data = null;
}
} else {
// Запрошено неизвестное окно
}
@ -343,7 +327,7 @@ class OrderController extends Controller
yii::$app->response->format = Response::FORMAT_JSON;
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)]),
'title' => 'Заказы',
'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)
*

View File

@ -33,6 +33,7 @@ class ProductController extends Controller
'allow' => true,
'actions' => [
'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
{
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
*/
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) {
// 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'];
// Инициализация cookie
$cookies = yii::$app->response->cookies;
// Инициализация аккаунта
$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;
return [
'products' => $this->renderPartial('/product/list', compact('account', 'products')),
'products' => $this->renderPartial('/product/list', compact('account', 'products', 'amount', 'page')),
'_csrf' => yii::$app->request->getCsrfToken()
];
}
@ -250,15 +281,15 @@ class ProductController extends Controller
*
* @param string $catn Артикул
*/
public function actionDelete(string $catn, string $prod): array|string|null
public function actionDelete(string $prod, string $catn): array|string|null
{
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
if (empty($catn) || empty($prod)) {
// Не получен артикул или производитель
// Запись кода ответа
yii::$app->response->statusCode = 500;
@ -355,8 +386,8 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
if (empty($catn) || empty($prod)) {
// Не получен артикул или производитель
// Запись кода ответа
yii::$app->response->statusCode = 500;
@ -368,49 +399,52 @@ class ProductController extends Controller
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)) {
// Существуют товары к которым планируется соединение
} else {
// Не существуют товары к которым планируется соединение
foreach (explode(',', $target_catn, 50) as $analog) {
// Перебор аналогов
// Инициализация товара
if ($to = [Product::writeEmpty((string) $target, $from->prod)]) {
// Удалось записать товар
foreach (explode('/', $analog, 50) as $value) {
// Перебор аналогов (дополнительная фильтрация)
// Запись в буфер возврата
$return['alert'] = "Записан новый товар: $target ($from->prod)";
} else {
// Не удалось записать товар
if ($to = Product::searchByCatnAndProd($value, $target_prod)) {
// Существуют товары к которым планируется соединение
} else {
// Не существуют товары к которым планируется соединение
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Инициализация товара
if ($to = [Product::writeEmpty($value, $target_prod, true)]) {
// Удалось записать товар
} else {
// Не удалось записать товар
// Запись в буфер возврата
$return['alert'] = "Не удалось записать новый товар: $target ($from->prod)";
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
// Запись в буфер возврата
$return['alert'] = "Не удалось записать новый товар: $value ($target_prod)";
// Переход в конец алгоритма
goto end;
}
}
// Инициализация количества созданных рёбер
$writed = 0;
foreach (is_array($to) ? $to : [$to] as $product) {
// Перебор товаров для записи связи: ТОВАР -> ТОВАР
// Универсализация данных (приведение к объекту)
if (is_array($product) && !$product = Product::searchByCatnAndProd($product['catn'], $product['prod'])) continue;
// Запись в группу
if ($from->connect($product)) $writed++;
}
}
}
// Инициализация количества созданных рёбер
$writed = 0;
foreach ($to as $product) {
// Перебор товаров для записи связи: ТОВАР -> ТОВАР
// Универсализация данных (приведение к объекту)
if (is_array($product) && !$product = Product::searchByCatnAndProd($product['catn'], $product['prod'])) continue;
// Запись ребра и синхронизация (добавление в группу к остальным аналогам)
if (count($from->synchronization($product)) > 0) $writed++;
}
// Запись в буфер возврата
$return['alert'] = "Создано $writed связей";
}
} else {
// Не найден товар
@ -433,6 +467,9 @@ class ProductController extends Controller
yii::$app->response->format = Response::FORMAT_JSON;
// Запись в буфер возврата
$return['list'] = $this->renderPartial('analogs', ['model' => $from]);
return $return;
}
@ -462,7 +499,7 @@ class ProductController extends Controller
];
if (empty($catn) || empty($prod)) {
// Не получен артикул
// Не получен артикул или производитель
// Запись кода ответа
yii::$app->response->statusCode = 500;
@ -471,35 +508,32 @@ class ProductController extends Controller
goto end;
}
if ($from = Product::searchByCatnAndProd($catn, $prod)) {
if ($target = Product::searchByCatnAndProd($catn, $prod)) {
// Товар найден
// Инициализация цели
$target = yii::$app->request->post('catn') ?? yii::$app->request->get('catn');
if ($target->disconnect()) {
// Удалено ребро (связь)
if ($from->disconnect($target)) {
// Удалено ребро (связь)
// Запись в буфер возврата
$return['disconnected'] = 1;
} else {
// Не удалено ребро (связь)
// Запись в буфер возврата
$return['alert'] = "Продукты успешно отсоединены";
} else {
// Не удалено ребро (связь)
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось удалить $catn ($prod) из группы";
// Запись в буфер возврата
$return['alert'] = "Не удалось отсоединить $target от $catn";
// Переход в конец алгоритма
goto end;
}
// Переход в конец алгоритма
goto end;
}
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар от когорого требуется отсоединение: $catn";
$return['alert'] = "Не удалось найти товар $catn ($prod)";
// Переход в конец алгоритма
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
{
// Инициализация

View File

@ -884,7 +884,7 @@ class ProfileController extends Controller
/**
* Поиск заявок на регистрацию
*/
public function actionPanelSuppliersRequestsSearch()
public function actionPanelSuppliersRequestsSearch(int $page = 1)
{
if (Yii::$app->request->isPost) {
// POST-запрос
@ -892,11 +892,15 @@ class ProfileController extends Controller
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 = [];
// Поиск заявок на регистрацию
$suppliers = Account::searchSuppliersRequests();
$suppliers = Account::searchSuppliersRequests($amount, $page, $order);
foreach ($suppliers as $account) {
// Перебор заявок
@ -1059,6 +1063,8 @@ class ProfileController extends Controller
foreach (Supply::searchByImport($import->readId(), limit: 9999) as $supply) {
// Перебор найденных поставок
if (empty($supply)) continue;
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()) {
// Удалено ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВКИ
@ -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 = [
'timer' => $timer,
'panel' => $this->renderPartial('/search/loading'),
'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken()
];
} else {
@ -159,6 +159,7 @@ class SearchController extends Controller
// Запись ответа
$return = [
'panel' => $this->renderPartial('/search/panel', compact('response', 'query')),
'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken()
];
@ -167,7 +168,7 @@ class SearchController extends Controller
// Запись ответа
$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;
}
}
@ -189,6 +190,7 @@ class SearchController extends Controller
return $return ?? [
'panel' => $this->renderPartial('/search/panel'),
'search_line_window_show' => 1,
'_csrf' => yii::$app->request->getCsrfToken()
];
} 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',
'geol',
'auth',
'acpt'
'acpt',
'chpk'
]
);
}
@ -98,7 +99,8 @@ class Account extends Document implements IdentityInterface, PartnerInterface
'vrfy' => 'Статус подтверждения владением почты',
'geol' => 'Геолокация',
'auth' => 'Аутентификационный хеш',
'acpt' => 'Согласие с офертой'
'acpt' => 'Согласие с офертой',
'chpk' => 'Ключ для смены пароля'
]
);
}
@ -212,6 +214,59 @@ class Account extends Document implements IdentityInterface, PartnerInterface
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С
*/
@ -669,18 +724,15 @@ class Account extends Document implements IdentityInterface, PartnerInterface
edge: 'account_edge_supply',
direction: 'OUTBOUND',
subquery_where: [
[
'account_edge_supply._from == account._id'
],
[
'account_edge_supply._to == "' . $_id . '"'
]
],
subquery_select: 'account',
where: 'account_edge_supply[0]._id != null',
limit: 1,
select: 'account_edge_supply[0]'
)[0];
select: 'account_edge_supply[0]',
limit: 1
)[0] ?? null;
}
/**
@ -918,9 +970,9 @@ class Account extends Document implements IdentityInterface, PartnerInterface
*
* @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 Аккаунт
*/
public static function initAccount(Account|int $account = null): ?static
public static function initAccount(Account|string|int $account = null): ?static
{
if (is_null($account)) {
// Данные аккаунта не переданы
if (yii::$app->user->isGuest) {
if (empty(yii::$app->user) || yii::$app->user->isGuest) {
// Аккаунт не аутентифицирован
} else {
// Аккаунт аутентифицирован
@ -955,6 +1007,15 @@ class Account extends Document implements IdentityInterface, PartnerInterface
if ($account = Account::searchById(Account::collectionName() . "/$account")) {
// Удалось инициализировать аккаунт
return $account;
}
} else if (is_string($account)) {
// Передан идентификатор документа (_id) (подразумевается)
// Инициализация (поиск в базе данных)
if ($account = Account::searchById($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')) {
// Исходящие рёбра
@ -204,7 +204,7 @@ abstract class Edge extends Document
} 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) {
@ -214,7 +214,7 @@ abstract class Edge extends Document
} 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 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(
from: 'warehouse',
@ -103,9 +103,9 @@ class Import extends Document
* @param Supply $supply Поставка
* @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(
from: 'supply',
@ -120,4 +120,27 @@ class Import extends Document
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
{
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 bool|string|null $html Содержимое уведомления (HTML или текст)
* @param string $account Получатель уведомления (_key)
* @param string $account Получатель уведомления (_key или "@...")
* @param string $type Тип уведомления
*
* @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;
$account ?? $account = yii::$app->user->identity->_key ?? throw new Exception('Не удалось инициализировать получателя');
$receiver = Account::initAccount($account)->_key ?? $account ?? throw new Exception('Не удалось инициализировать получателя');
if ((bool) (int) $html) {
// Получен текст в формете HTML-кода
@ -176,7 +176,7 @@ class Notification extends Document
// Уведомление записано
// Инициализация получателей и создание ребра
self::searchReceiverAndConnect($model, $account, $type);
self::searchReceiverAndConnect($model, $receiver, $type);
}
return null;

View File

@ -273,7 +273,8 @@ class Order extends Document implements DocumentInterface
bool $supplies = false,
int|null $from = null,
int|null $to = null,
bool $count = false
bool $count = false,
bool $debug = false
): int|array|null {
// Инициализация аккаунта
if (empty($account)) {
@ -350,9 +351,15 @@ class Order extends Document implements DocumentInterface
sort: ['DESC'],
select: $select,
direction: 'INBOUND',
count: !$supplies && $count
count: !$supplies && $count,
debug: $debug
);
if ($debug) {
var_dump($orders);
die;
}
if (!$supplies && $count) {
// Запрошен подсчет заказов
@ -363,7 +370,7 @@ class Order extends Document implements DocumentInterface
$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'];
} else {
// Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord)
$product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']);
// Инициализация доставки Dellin (автоматическая)
@ -548,6 +556,7 @@ class Order extends Document implements DocumentInterface
$product->update();
}
// Запись цены (цена поставки + цена доставки + наша наценка)
$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) {

View File

@ -457,7 +457,7 @@ class Product extends Document
where: 'supply_edge_product[0]._id != null',
limit: 1,
select: 'supply_edge_product[0]'
)[0];
)[0] ?? null;
}
/**
@ -606,20 +606,6 @@ class Product extends Document
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]'
)[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
{
// Удаление товара из группы (подразумевается, что будет только одно)
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', [
@ -138,9 +138,9 @@ class ProductGroup extends Document implements GroupInterface
*
* @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 Ограничение по максимальному количеству
*/
public function searchProducts(int $limit = 999): ?array
public function searchProducts(int $limit = 100, int $page = 1): ?array
{
// Инициализация буфера товаров
$products = [];
foreach ($this->searchEdges($limit) as $edge) {
foreach ($this->searchEdges($limit, $page) as $edge) {
// Перебор рёбер
$products[] = Product::searchById($edge->_from);
@ -191,6 +191,20 @@ class ProductGroup extends Document implements GroupInterface
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;
// Генерация сдвига по запрашиваемым данным (пагинация)
// Генерация сдвига по запрашиваемым данным (система страниц)
$offset = $limit * ($page - 1);
foreach ($response as &$row) {
@ -167,7 +167,7 @@ class Search extends Document
// Инициализация буфера
$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\Settings;
use app\models\Import;
use app\models\File;
use app\models\ImportEdgeSupply;
use app\models\WarehouseEdgeImport;
use app\models\ImportEdgeFile;
use carono\exchange1c\interfaces\OfferInterface;
use carono\exchange1c\interfaces\ProductInterface;
@ -23,6 +25,8 @@ use moonland\phpexcel\Excel;
use DateTime;
use DateTimeZone;
use DatePeriod;
use DateInterval;
use Exception;
@ -38,13 +42,6 @@ class Supply extends Product implements ProductInterface, OfferInterface
{
use Xml2Array;
/**
* Количество
*
* Используется при выводе в корзине
*/
public int $amnt = 0;
/**
* Имя коллекции
*/
@ -101,14 +98,21 @@ class Supply extends Product implements ProductInterface, OfferInterface
*/
public function afterSave($data, $vars): void
{
if (AccountEdgeSupply::searchByVertex(yii::$app->user->id, $this->readId())) {
// Ребро: "АККАУНТ -> ПОСТАВКА" уже существует
// Инициализация
$account = Account::initAccount();
} else {
// Ребра не существует
if (isset($account)) {
// Инициализирован аккаунт
// Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write(yii::$app->user->id, $this->readId(), 'import');
if (AccountEdgeSupply::searchByVertex($account->readId(), $this->readId())) {
// Ребро: "АККАУНТ -> ПОСТАВКА" уже существует
} else {
// Ребра не существует
// Запись ребра: АККАУНТ -> ПОСТАВКА
(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) {
// Перебор найденных рёбер
// Поиск поставки и запись в буфер вывода
$supplies[] = static::searchById($edge->_to);
// Поиск поставки
$supply = static::searchById($edge->_to);
if (empty($supply)) continue;
// Запись поставки в буфер вывода
$supplies[] = $supply;
}
return $supplies;
@ -387,20 +396,15 @@ class Supply extends Product implements ProductInterface, OfferInterface
}
/**
* Запись поставок из excel
*
* На данный момент обрабатывает только импорт из
* файлов с расширением .excel
* Импорт Excel-файла
*
* @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
{
// Инициализация
$data = [];
$created = 0;
$updated = 0;
$account = Account::initAccount($account);
if ($this->validate()) {
@ -425,9 +429,85 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (!mkdir($path, 0775, true))
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,
'setIndexSheetByName' => true,
]);
@ -441,7 +521,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (count($data) < 1) {
// Не найдены строки с товарами
$this->addError('errors', 'Не удалось найти данные товаров');
$supply->addError('errors', 'Не удалось найти данные товаров');
} 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 (!$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);
@ -660,6 +741,9 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Перенос данных в буфер (существующий в базе данных дубликат)
$supply->setAttributes($vars, false);
// Запись ребра: АККАУНТ -> ПОСТАВКА
(new AccountEdgeSupply)->write($account->readId(), $supply->readId(), 'import');
// Перезапись существующего документа
$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;
@ -689,7 +773,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
// }
// Добавление в группу аналогов
$group->writeProduct($product);
// $group->writeProduct($product);
}
return !$error;
@ -698,12 +782,12 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Запись поставки
$create($article, (int) $amount);
foreach ($analogs as $_supply) {
// Перебор аналогов (если найдены)
// foreach ($analogs as $_supply) {
// // Перебор аналогов (если найдены)
// Запись поставки
$create((string) $_supply);
}
// // Запись поставки
// $create((string) $_supply);
// }
}
}
}
@ -712,26 +796,14 @@ class Supply extends Product implements ProductInterface, OfferInterface
if (count($imported) > 0) {
// Успешно записана минимум 1 поставка
// Инициализация инстанции импорта
$import = new Import;
foreach ($imported as $supply) {
// Перебор импортированных поставок
$import->file = $path;
$import->name = $filename;
// Инициализация инстанции импорта
$import = Import::searchByFile($file);
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) {
// Перебор импортированных поставок
if (isset($import)) {
// Найдена интанция импорта
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 для формы в представлении)
$this->file_excel = null;
return true;
$supply->file_excel = null;
}
// Запись ошибки
$this->addError('errors', 'Не пройдена проверка параметров');
// Макрос действий после импорта
static::afterImportExcel($created, $updated);
// Удаление (важно именно задать null для формы в представлении)
$this->file_excel = null;
$supply->addError('errors', 'Не пройдена проверка параметров');
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 $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;
$account = yii::$app->user->identity;
$account = Account::initAccount($account);
// Инициализация часового пояса
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');
// Настройка
$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->account = $account->readId();
// Отправка
return (bool) $model->write();

View File

@ -78,6 +78,7 @@ use app\models\AccountForm;
</div>
<?= 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

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>
</div>
<div class="col-2 my-auto mr-3 text-right">
{$supply['cost']} {$supply['currency']}
{$supply['supply']->cost} {$supply['currency']}
</div>
</div>
<div class="dropdown-divider"></div>
@ -223,20 +223,12 @@ use DateTime;
<script src="/js/textarea.js" defer></script>
<script src="/js/cart.js" defer></script>
<script src="/js/profile.js" defer></script>
<script defer>
if (document.readyState === "complete") {
<script>
document.addEventListener('cart.loaded', function(e) {
cart_cost_calculate();
cart_registration_entity_init(<?= $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>

View File

@ -5,10 +5,10 @@ declare(strict_types=1);
$this->title = 'SkillParts';
?>
<link href="/css/ticker.css" rel="stylesheet">
<link href="/css/hotline.css" rel="stylesheet">
<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">
<h1 class="mb-4 ml-0 gilroy">Проблема с подбором запчастей?</h1>
<p class="ml-0 d-flex">
@ -21,22 +21,22 @@ $this->title = 'SkillParts';
</div>
</section>
<section class="h-100 d-flex ticker unselectable">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/cummins.png" alt="Cummins">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/iveco.png" alt="Iveco">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/case.png" alt="Case">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/perkins.png" alt="Perkins">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shantui.png" alt="Shantui">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG">
<img class="w-auto h-100 mr-3 my-auto" src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi">
<section id="hotline" class="py-4 hotline unselectable" data-hotline="true" data-hotline-step="1">
<article><img src="/img/logos/h32px/compressed/cummins.png" alt="Cummins"></article>
<article><img src="/img/logos/h32px/compressed/iveco.png" alt="Iveco"></article>
<article><img src="/img/logos/h32px/compressed/komatsu.png" alt="Komatsu"></article>
<article><img src="/img/logos/h32px/compressed/case.png" alt="Case"></article>
<article><img src="/img/logos/h32px/compressed/isuzu.png" alt="Isuzu"></article>
<article><img src="/img/logos/h32px/compressed/new_holland.png" alt="New Holland"></article>
<article><img src="/img/logos/h32px/compressed/perkins.png" alt="Perkins"></article>
<article><img src="/img/logos/h32px/compressed/john_deere.png" alt="John Deere"></article>
<article><img src="/img/logos/h32px/compressed/caterpillar.png" alt="Caterpillar"></article>
<article><img src="/img/logos/h32px/compressed/shantui.png" alt="Shantui"></article>
<article><img src="/img/logos/h32px/compressed/xcmg.png" alt="XCMG"></article>
<article><img src="/img/logos/h32px/compressed/kobelco.png" alt="Kobelco"></article>
<article><img src="/img/logos/h32px/compressed/shehwa.png" alt="SHEHWA"></article>
<article><img src="/img/logos/h32px/compressed/bomag.png" alt="BOMAG"></article>
<article><img src="/img/logos/h32px/compressed/hitachi.png" alt="Hitachi"></article>
</section>
<section class="container mb-4">
@ -80,5 +80,13 @@ $this->title = 'SkillParts';
</section>
</div>
<script src="/js/ticker.js" defer></script>
<script src="/js/hotline.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
use app\models\Settings;
use app\models\Product;
?>
<!DOCTYPE html>
@ -133,9 +134,7 @@ use app\models\Settings;
<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;" colspan="3" 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="6" 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>
@ -156,11 +155,13 @@ use app\models\Settings;
<?php foreach ($data['supplies'] as $prod => $supplies) : ?>
<?php foreach ($supplies as $catn => $deliveries) : ?>
<?php foreach ($deliveries as $delivery => $supply) : ?>
<?php
// Инициализация названия
$name = Product::searchByCatn($catn)->name ?? 'Без названия';
?>
<tr>
<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="3" valign="center"><?= $catn ?></td>
<td style="text-align: left; border: solid;" colspan="2" valign="center"><?= $supply['account']['indx'] ?></td>
<td style="text-align: left; border: solid;" colspan="6" valign="center"><?= $prod ?> <?= $catn ?> <?= $name ?></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['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>
</div>
<div class="h-divider"></div>
<script src="/js/js.cookie.min.js" defer></script>
</header>
<aside class="container mb-4">
@ -110,6 +111,7 @@ AppAsset::register($this);
</div>
</footer>
<script src="/js/loading.js" defer></script>
<?php $this->endBody() ?>
</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>
</div>
<div style="background: #fff;">
<small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small>
</br>
<small>Если это были не вы, проверьте безопасность ваших аккаунтов и свяжитесь с администрацией</small>
<small>Если это были не вы свяжитесь с администрацией</small>
</div>
</div>

View File

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

View File

@ -40,170 +40,13 @@ if (empty($window)) {
<label class="btn button_white mb-0" for="orders_panel_orders">Мои заказы</label>
</div>
<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">
<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) : ?>
<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 ?>
</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>
<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($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'])) {
// Не удалось рассчитать доставку
@ -340,16 +218,8 @@ if (empty($window)) {
} 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();
}
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
// Инициализация времени доставки
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) {
// Инициализация даты отправки
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
$delivery_converted = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspReceiver'])->getTimestamp();
$delivery_converted = DateTime::createFromFormat('Y-m-d', $order_edge_supply[0]->dlvr['data']['orderDates']['arrivalToOspReceiver'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y');
}
$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')
) : ?>
<script src="/js/orders_panel.js" defer></script>
<script>
document.addEventListener('orders_panel.loaded', function(e) {
// Загружена программа для работы с заказами
// Инициализация списка аналогов
order_init_list(8, 1);
});
</script>
<?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 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)) {
echo <<<HTML
@ -71,8 +73,7 @@ use app\models\Product;
// Перебор изображений для генерации полных версий
// Инициализация
$prod = $image['prod'] ?? 'Неизвестный';
$orig = $image['orig'] ?? '/img/covers/product.png';
$orig = $image['orig'] ?? Product::cover($model['prod'], 0);
if (($image['covr'] ?? false) || ($covr_not_found && $key === 0)) {
// Если это изображение является обложкой или обложка не найдена
@ -116,22 +117,6 @@ use app\models\Product;
?>
</div>
<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">
<?php if (
!yii::$app->user->isGuest
@ -148,12 +133,15 @@ use app\models\Product;
<?= $model['prod'] ?? 'Неизвестно' ?>
</h3>
<?php else : ?>
<h1 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto product_catn">
<?= $model['catn'] ?>
</h1>
<h2 id="prod_<?= $model['catn'] ?>" class="mt-auto my-0 product_prod">
<?= $model['prod'] ?? 'Неизвестно' ?>
<h2 id="name_<?= $model['catn'] ?>" class="mt-auto my-0 w-100 mb-1 product_name">
<?= $model['name'] ?? 'Неизвестно' ?>
</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 ?>
</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>
<?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 ?>
</div>
@ -211,6 +199,8 @@ use app\models\Product;
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) {
echo '<div class="row">';
// Инициализация артикула
$catn = $model['catn'];
$prod = $model['prod'];
@ -219,15 +209,21 @@ use app\models\Product;
// Товар активен
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;
} else {
// Товар неактивен, либо что-то с ним ещё
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;
}
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>
@ -238,6 +234,18 @@ use app\models\Product;
<time class="ml-auto"></time>
</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>
</div>
</div>
@ -250,3 +258,21 @@ use app\models\Product;
) : ?>
<script src="/js/product_panel.js" defer></script>
<?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 DateTimeZone;
// Инициализация счетчика аккаунтов
$amount = 0;
// Инициализация счетчика товаров
$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 ($products as $product) : ?>
<?php
@ -34,7 +38,7 @@ $timezone = $timezone[1][0];
?>
<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 ?? 'Неизвестно' ?>">
<?= $product->catn ?? 'Неизвестно' ?>
<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>
</div>
<?php if ($amount < count($products)) : ?>
<?php if ($i < count($products) + $amount * ($page - 1)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?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_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_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>
</div>
<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 ?> />
<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_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_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_choose('profile_panel_input_accounts_suppliers');">Поставщики</label>
</div>
<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">
<h5>Список пользователей</h5>
<div class="dropdown-divider mb-4"></div>
<div id="profile_panel_input_accounts_list" class="px-3">
<?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 id="profile_panel_input_accounts_list" class="px-3"></div>
</div>
<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 ?> />
<div class="col">
<h5>Список товаров</h5>
<div class="col-auto 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', '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', 'inactive');">Неактивные</a>
<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', '@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', '@last', '@last', 1, true);">Активные</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 class="dropdown-divider mb-4"></div>
<div id="profile_panel_input_products_wrap"></div>
@ -172,76 +123,7 @@ $timezone = $timezone[1][0];
<div class="col">
<h5>Список поставок</h5>
<div class="dropdown-divider mb-4"></div>
<?php
// Инициализация счетчика аккаунтов
$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 id="profile_panel_input_supplies_wrap"></div>
</div>
<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/profile_panel.js" defer></script>
<script src="/js/textarea.js" defer></script>
<script defer>
document.addEventListener('DOMContentLoaded', function() {
// Загружен документ
<script>
document.addEventListener('profile.loaded', function(e) {
// Загружена программа для работы с профилем
});
document.addEventListener('profile_panel.loaded', function(e) {
// Загружена программа для работы с панелью управления
// Инициализация активной вкладки
page_profile_panel_choose('<?= $panel ?? '' ?>');
});
document.addEventListener('textarea.loaded', function(e) {
// Загружена программа для работы с блоками текста
// Инициализация панели для ввода текста (тест уведомлений для администратора)
initTextarea(
@ -394,5 +284,5 @@ $timezone = $timezone[1][0];
send.disabled = false;
send_html.disabled = false;
});
}, false);
});
</script>

View File

@ -9,6 +9,7 @@ use app\models\Account;
use app\models\Import;
use app\models\Settings;
use app\models\Warehouse;
use app\models\File;
use DateTime;
use DateTimeZone;
@ -103,9 +104,12 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
}
}
// Инициализация файла
$file = File::searchByImport($import);
// Инициализация ссылки на скачивание
preg_match_all('/\/files\/.*$/', $import->file, $matches);
$download = $matches[0][0];
preg_match_all('/\/files\/.*$/', $file->path ?? '', $matches);
$download = $matches[0][0] ?? '';
?>
<div class="mb-3 row">
@ -113,7 +117,7 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
<?= ++$amount ?>.
</div>
<div class="col">
<?= $import->name ?? 'Без названия' ?>
<?= (isset($file->stts) && $file->stts === 'loaded') ? $file->name ?? 'Без названия' : ($file->name ?? 'Без названия') . ' (в очереди на загрузку)' ?>
</div>
<div class="col-3 px-0 text-right">
<?= $create ?? 'Неизвестно' ?>
@ -172,16 +176,19 @@ $panel ?? $panel = 'profile_panel_supplies_input_import';
<?php foreach (Import::searchByWarehouse($warehouse) as $import) : ?>
<?php
// Инициализация файла
$file = File::searchByImport($import);
// Инициализация ссылки на скачивание
preg_match_all('/\/files\/.*$/', $import->file, $matches);
$download = $matches[0][0];
preg_match_all('/\/files\/.*$/', $file->path ?? '', $matches);
$download = $matches[0][0] ?? '';
?>
<div class="mx-2 mb-3 row">
<div class="col-auto">
<?= ++$amount_imports ?>.
</div>
<div class="col">
<?= $import->name ?? 'Без названия' ?>
<?= (isset($file->stts) && $file->stts === 'loaded') ? $file->name ?? 'Без названия' : ($file->name ?? 'Без названия') . ' (в очереди на загрузку)' ?>
</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="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;
@ -98,8 +98,9 @@ use app\models\Search;
<?php foreach ($catns as $catn => $products) : ?>
<?php foreach ($products as $product) : ?>
<?php
// Проверка на дубликат
if (array_key_exists($product['catn'], $writed[$prod])) continue;
if (isset($writed[$prod]) && array_key_exists($product['catn'], $writed[$prod])) continue;
// Инициализация данных товара
$covr = null;
@ -144,9 +145,7 @@ use app\models\Search;
<?php else : ?>
<?php if ($advanced ?? false) : ?>
<h1 class="m-auto gilroy text-center"><b>Ничего не найдено</b></h1>
<?php else : ?>
<div class="row py-3 w-100">

View File

@ -44,9 +44,10 @@ if (isset($history) && $history) {
// Перебор найденных данных
$catn = $row['catn'];
$prod = $row['prod'];
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;
}
} 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 article>div {
#page_product article>div:not(#products_list) {
background-color: #fff;
}
@ -95,3 +95,20 @@
#page_product article .product_admin_menu {
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() {
$.ajax({
url: '/deauthentication',

View File

@ -653,3 +653,5 @@ function cart_response_error(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;
// alert('Основной блок на ' + data.main);
// Реинициализация
reinitialization(main);
};
// Заголовок

View File

@ -25,6 +25,71 @@ function order_init(order) {
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) {
$.ajax({
@ -591,3 +656,9 @@ function orders_supply_status_edit(order, prod, catn, delivery, element) {
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) {
// Обработка ответов
@ -41,3 +107,6 @@ function product_response_error(data, status, xhr) {
product_response(data, status, xhr);
};
// Запись (сброс) cookie с номером страницы
Cookies.set('product_analogs_page', 1, { path: window.location.pathname });

View File

@ -254,7 +254,7 @@ function product_panel_dimensions_save(catn, prod, element, dimension) {
function product_panel_weight_edit(catn, prod, element) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined && element !== null && element !== undefined) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined && element !== null && element !== undefined) {
let input = document.createElement('input');
@ -327,7 +327,7 @@ function product_panel_weight_save(catn, prod, element) {
};
function product_panel_description_edit(catn, prod, element) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined && element !== null && element !== undefined) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined && element !== null && element !== undefined) {
element.innerHTML = '<textarea class="form-control" cols="50" rows="5" onchange = "return product_panel_description_save(\'' + catn + '\', \'' + prod + '\', this.parentElement)">' + element.innerText + '</textarea>';
element.removeAttribute('onclick');
@ -388,25 +388,29 @@ function product_panel_description_save(catn, prod, element) {
* @param {*} catn
* @returns
*/
function product_panel_connect(catn, prod, group) {
if (group === null || group === '') {
return false;
}
if (group === undefined) {
// Не получены данные о группе
group = prompt('Подключить аналог');
}
function product_panel_connect(catn, prod, target_catn, target_prod) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
if (target_catn === undefined) {
// Не получены данные о группе
target_catn = prompt('Артикул');
}
if (target_prod === undefined) {
// Не получены данные о группе
target_prod = prompt('Производитель');
}
$.ajax({
url: '/product/' + prod + '/' + catn + '/connect',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'catn': group
'catn': target_catn,
'prod': target_prod
},
success: product_response_success,
error: product_response_error
@ -418,24 +422,35 @@ function product_panel_connect(catn, prod, group) {
return true;
}
/**
* Отключение от группы
*
* @param {*} catn
* @returns
*/
function product_panel_disconnect(catn, prod) {
if (catn !== null && catn !== undefined) {
function product_panel_disconnect(catn, prod, element) {
if (typeof prod === 'string' && typeof catn === 'string' && typeof element === 'object') {
$.ajax({
url: '/product/' + prod + '/' + catn + '/disconnect',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'catn': prompt('Отсоединить аналог (пустое поле - все аналоги)')
'_csrf': yii.getCsrfToken()
},
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
});
@ -443,10 +458,10 @@ function product_panel_disconnect(catn, prod) {
};
return true;
}
}
function product_panel_delete(catn, prod) {
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
if (typeof prod === 'string' && typeof catn === 'string') {
$.ajax({
url: '/product/' + prod + '/' + catn + '/delete',
type: 'post',

View File

@ -393,3 +393,5 @@ function page_profile_response_error(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_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).removeAttribute('onclick');
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) {
@ -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_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_control').setAttribute('onclick', 'return page_profile_panel_accounts_choose(\'profile_panel_input_accounts_control\');');
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_choose(\'profile_panel_input_accounts_control\');');
document.querySelector('[for="profile_panel_input_accounts_suppliers"]').classList.remove('active');
document.querySelector('[for="profile_panel_input_accounts_control"]').classList.remove('active');
if (button === 'disable') {
return;
} else if (button === 'profile_panel_input_accounts_control') {
// Блок: "пользователи"
// Генерация списка
profile_panel_accounts_read(1, true);
} 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');
}
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({
url: '/profile/panel/suppliers/requests/search',
url: '/profile/panel/suppliers/requests/search/' + page,
type: 'post',
dataType: 'json',
data: {
@ -153,6 +173,9 @@ function page_profile_panel_input_suppliers_requests_init(wrap = 'profile_panel_
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;
@ -162,6 +185,26 @@ function page_profile_panel_input_suppliers_requests_init(wrap = 'profile_panel_
// Запись в документ
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
if (search.length > 1) {
@ -754,19 +797,52 @@ function profile_panel_products_read(search = '@last', type = '@last', from = '@
// Запрос
$.ajax({
url: '/products/read/' + type,
url: '/products/read/' + type + '/' + page,
type: 'post',
dataType: 'json',
data: data,
success: (data, status, xhr) => {
// Инициализация оболочки
let element = document.getElementById('profile_panel_input_products_wrap');
if (data.products !== undefined) {
// Передан список
if (data.products !== undefined && element !== null) {
// Передан список продуктов и найден элемент-оболочка продуктов
// Инициализация оболочки
let element = document.getElementById('profile_panel_input_products_wrap');
// Запись полученного содержимого
element.innerHTML = data.products;
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;
} 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);
@ -777,3 +853,137 @@ function profile_panel_products_read(search = '@last', type = '@last', from = '@
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;
};
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({
url: '/search',
type: 'post',
@ -38,10 +38,10 @@ function product_search_history(text = '', advanced = 0) {
'history': true
},
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) {
search_panel_error(text, advanced, data, status, xhr);
search_panel_error('', 0, data, status, xhr);
},
statusCode: search_panel_statusCode
});
@ -68,45 +68,84 @@ function search_panel_hide(line = 'search_line', panel = 'search_line_window') {
};
function search_panel_response(text, advanced, data, status, xhr) {
// Отображение окна поиска
if (data.search_line_window_show === 1) {
search_panel_show();
};
// Сокрытие окна поиска
if (data.search_line_window_hide === 1) {
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) {
panel = document.getElementById('search_line_window');
// Получены данные для окна поиска
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 (data.timer !== undefined) {
// Ожидание перед повторным запросом
if (document.getElementById('loading_search') === null) {
// Не найден индикатор загрузки поиска
if (getCookie('search') !== 'processing') {
// На данный момент нет других запросов поиска
// Инициализация панели
let panel = document.getElementById('search_line_window');
// Запись о существовании запроса
search_panel_statusCode_progress();
// Очистка
panel.innerHTML = null;
// Запрос
setTimeout(product_search, data.timer + '000', text, advanced);
};
};
// Отображение окна поиска
if (data.search_line_window_show === 1) {
search_panel_show();
};
// Сокрытие окна поиска
if (data.search_line_window_hide === 1) {
search_panel_hide();
};
// Запись индикатора загрузки
panel.appendChild(generate('search', 'Поиск'));
}
}
};
main_response(data, status, xhr);
@ -129,19 +168,21 @@ function search_panel_error(text, advanced, data, status, xhr) {
function search_panel_statusCode() {
return {
200: search_panel_statusCode_waiting,
404: search_panel_statusCode_waiting,
200: search_panel_statusCode_awaiting,
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";
};
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";
};

View File

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