крупное обновление всего
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -88,7 +88,6 @@ class CartController extends Controller
|
|||
// Поиск корзины (текущего заказа)
|
||||
$data = Order::searchSmart()[0] ?? null;
|
||||
|
||||
|
||||
if (empty($data['order'])) {
|
||||
// Корзина не инициализирована
|
||||
|
||||
|
|
|
@ -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,37 +298,11 @@ 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',
|
||||
limit: 10,
|
||||
page: 1,
|
||||
supplies: true
|
||||
);
|
||||
|
||||
// Инициализация панели модератора заказов
|
||||
if (!yii::$app->user->isGuest && Account::isMinimalAuthorized($account)) {
|
||||
// Имеет доступ пользователь
|
||||
|
||||
// Инициализация заказов для модератора
|
||||
$moderator_data = Order::searchSmart(
|
||||
account: '@all',
|
||||
stts: $stts,
|
||||
limit: 10,
|
||||
page: 1,
|
||||
|
@ -322,12 +311,7 @@ class OrderController extends Controller
|
|||
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)
|
||||
*
|
||||
|
|
|
@ -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,20 +399,24 @@ 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)) {
|
||||
foreach (explode(',', $target_catn, 50) as $analog) {
|
||||
// Перебор аналогов
|
||||
|
||||
foreach (explode('/', $analog, 50) as $value) {
|
||||
// Перебор аналогов (дополнительная фильтрация)
|
||||
|
||||
if ($to = Product::searchByCatnAndProd($value, $target_prod)) {
|
||||
// Существуют товары к которым планируется соединение
|
||||
} else {
|
||||
// Не существуют товары к которым планируется соединение
|
||||
|
||||
// Инициализация товара
|
||||
if ($to = [Product::writeEmpty((string) $target, $from->prod)]) {
|
||||
if ($to = [Product::writeEmpty($value, $target_prod, true)]) {
|
||||
// Удалось записать товар
|
||||
|
||||
// Запись в буфер возврата
|
||||
$return['alert'] = "Записан новый товар: $target ($from->prod)";
|
||||
} else {
|
||||
// Не удалось записать товар
|
||||
|
||||
|
@ -389,7 +424,7 @@ class ProductController extends Controller
|
|||
yii::$app->response->statusCode = 500;
|
||||
|
||||
// Запись в буфер возврата
|
||||
$return['alert'] = "Не удалось записать новый товар: $target ($from->prod)";
|
||||
$return['alert'] = "Не удалось записать новый товар: $value ($target_prod)";
|
||||
|
||||
// Переход в конец алгоритма
|
||||
goto end;
|
||||
|
@ -399,18 +434,17 @@ class ProductController extends Controller
|
|||
// Инициализация количества созданных рёбер
|
||||
$writed = 0;
|
||||
|
||||
foreach ($to as $product) {
|
||||
foreach (is_array($to) ? $to : [$to] as $product) {
|
||||
// Перебор товаров для записи связи: ТОВАР -> ТОВАР
|
||||
|
||||
// Универсализация данных (приведение к объекту)
|
||||
if (is_array($product) && !$product = Product::searchByCatnAndProd($product['catn'], $product['prod'])) continue;
|
||||
|
||||
// Запись ребра и синхронизация (добавление в группу к остальным аналогам)
|
||||
if (count($from->synchronization($product)) > 0) $writed++;
|
||||
// Запись в группу
|
||||
if ($from->connect($product)) $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,17 +508,14 @@ 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 ($from->disconnect($target)) {
|
||||
if ($target->disconnect()) {
|
||||
// Удалено ребро (связь)
|
||||
|
||||
// Запись в буфер возврата
|
||||
$return['alert'] = "Продукты успешно отсоединены";
|
||||
$return['disconnected'] = 1;
|
||||
} else {
|
||||
// Не удалено ребро (связь)
|
||||
|
||||
|
@ -489,7 +523,7 @@ class ProductController extends Controller
|
|||
yii::$app->response->statusCode = 500;
|
||||
|
||||
// Запись в буфер возврата
|
||||
$return['alert'] = "Не удалось отсоединить $target от $catn";
|
||||
$return['alert'] = "Не удалось удалить $catn ($prod) из группы";
|
||||
|
||||
// Переход в конец алгоритма
|
||||
goto end;
|
||||
|
@ -499,7 +533,7 @@ class ProductController extends Controller
|
|||
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
|
||||
{
|
||||
// Инициализация
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
// Запись в буфер вывода реинициализированного элемента
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запись рёбер групп
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
if (isset($account)) {
|
||||
// Инициализирован аккаунт
|
||||
|
||||
if (AccountEdgeSupply::searchByVertex($account->readId(), $this->readId())) {
|
||||
// Ребро: "АККАУНТ -> ПОСТАВКА" уже существует
|
||||
|
||||
} else {
|
||||
// Ребра не существует
|
||||
|
||||
// Запись ребра: АККАУНТ -> ПОСТАВКА
|
||||
(new AccountEdgeSupply)->write(yii::$app->user->id, $this->readId(), 'import');
|
||||
(new AccountEdgeSupply)->write($account->readId(), $this->readId(), 'import');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,8 +175,13 @@ class Supply extends Product implements ProductInterface, OfferInterface
|
|||
foreach (ImportEdgeSupply::find()->where(['_from' => $id])->limit($limit)->all() as $edge) {
|
||||
// Перебор найденных рёбер
|
||||
|
||||
// Поиск поставки и запись в буфер вывода
|
||||
$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,27 +796,15 @@ class Supply extends Product implements ProductInterface, OfferInterface
|
|||
if (count($imported) > 0) {
|
||||
// Успешно записана минимум 1 поставка
|
||||
|
||||
// Инициализация инстанции импорта
|
||||
$import = new Import;
|
||||
|
||||
$import->file = $path;
|
||||
$import->name = $filename;
|
||||
|
||||
if ($import->save()) {
|
||||
// Инстанция импорта успешно загружена
|
||||
|
||||
if (WarehouseEdgeImport::write(Warehouse::collectionName() . "/$warehouse", $import->readId(), data: ['type' => 'loaded'])) {
|
||||
// Записано ребро: СКЛАД -> ИНСТАНЦИЯ ПОСТАВОК
|
||||
|
||||
// Запись в журнал инстанции импорта
|
||||
$import->journal('connect_with_warehouse', [
|
||||
'target' => Warehouse::collectionName() . "/$warehouse"
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($imported as $supply) {
|
||||
// Перебор импортированных поставок
|
||||
|
||||
// Инициализация инстанции импорта
|
||||
$import = Import::searchByFile($file);
|
||||
|
||||
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();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ?>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
<div style="background: #fff;">
|
||||
<small>Вы получили это сообщение потому, что на ваш почтовый адрес была совершена регистрация</small>
|
||||
</br>
|
||||
<small>Если это были не вы, проверьте безопасность ваших аккаунтов и свяжитесь с администрацией</small>
|
||||
<small>Если это были не вы свяжитесь с администрацией</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 ?>
|
||||
|
|
|
@ -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 ?>
|
|
@ -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 ?>
|
|
@ -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>
|
||||
|
|
|
@ -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 ?>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ?>
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 860 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 771 B |
After Width: | Height: | Size: 619 B |
After Width: | Height: | Size: 907 B |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 942 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
|
@ -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',
|
||||
|
|
|
@ -653,3 +653,5 @@ function cart_response_error(data, status, xhr) {
|
|||
|
||||
cart_response(data, status, xhr);
|
||||
};
|
||||
|
||||
document.dispatchEvent(new CustomEvent("cart.loaded"));
|
||||
|
|
|
@ -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 }
|
||||
})
|
||||
);
|
|
@ -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:"/"})}));
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -12,11 +12,6 @@ function main_response(data, status, xhr) {
|
|||
|
||||
// Обновление документа
|
||||
main.innerHTML = data.main;
|
||||
|
||||
// alert('Основной блок на ' + data.main);
|
||||
|
||||
// Реинициализация
|
||||
reinitialization(main);
|
||||
};
|
||||
|
||||
// Заголовок
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
function product_panel_connect(catn, prod, target_catn, target_prod) {
|
||||
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
|
||||
|
||||
if (group === undefined) {
|
||||
if (target_catn === undefined) {
|
||||
// Не получены данные о группе
|
||||
|
||||
group = prompt('Подключить аналог');
|
||||
target_catn = prompt('Артикул');
|
||||
}
|
||||
|
||||
if (target_prod === undefined) {
|
||||
// Не получены данные о группе
|
||||
|
||||
target_prod = prompt('Производитель');
|
||||
}
|
||||
|
||||
if (catn !== null && catn !== undefined && prod !== null && prod !== undefined) {
|
||||
$.ajax({
|
||||
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',
|
||||
|
|
|
@ -393,3 +393,5 @@ function page_profile_response_error(data, status, xhr) {
|
|||
|
||||
page_profile_response(data, status, xhr);
|
||||
};
|
||||
|
||||
document.dispatchEvent(new CustomEvent("profile.loaded"));
|
||||
|
|
|
@ -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) => {
|
||||
if (data.products !== undefined) {
|
||||
// Передан список
|
||||
|
||||
// Инициализация оболочки
|
||||
let element = document.getElementById('profile_panel_input_products_wrap');
|
||||
|
||||
if (data.products !== undefined && element !== null) {
|
||||
// Передан список продуктов и найден элемент-оболочка продуктов
|
||||
if (element !== null) {
|
||||
// Найден элемент-оболочка
|
||||
|
||||
// Запись cookie с номером страницы
|
||||
Cookies.set('profile_panel_products_read_page', parseInt(Cookies.get('profile_panel_products_read_page') ?? 1) + 1, { path: window.location.pathname });
|
||||
|
||||
if (purge || element.children.length === 0) {
|
||||
// Не найдены строки, либо запрошена очистка
|
||||
|
||||
// Запись полученного содержимого
|
||||
element.innerHTML = data.products;
|
||||
} 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"));
|
||||
|
|
|
@ -34,3 +34,12 @@ function reinitialization(target) {
|
|||
|
||||
return false;
|
||||
};
|
||||
|
||||
new MutationObserver(function () {
|
||||
reinitialization(document.getElementsByTagName('main')[0]);
|
||||
}).observe(
|
||||
document.getElementsByTagName('main')[0],
|
||||
{
|
||||
childList: true,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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,36 +68,6 @@ function search_panel_hide(line = 'search_line', panel = 'search_line_window') {
|
|||
};
|
||||
|
||||
function search_panel_response(text, advanced, data, status, xhr) {
|
||||
|
||||
if (data !== undefined) {
|
||||
// Получены данные с сервера
|
||||
|
||||
// Окно поиска
|
||||
if (data.panel !== undefined) {
|
||||
panel = document.getElementById('search_line_window');
|
||||
|
||||
// Обновление окна результатов поиска
|
||||
panel.innerHTML = data.panel;
|
||||
|
||||
// Реинициализация
|
||||
reinitialization(panel);
|
||||
};
|
||||
|
||||
// Таймер для повтора запроса
|
||||
if (data.timer !== undefined) {
|
||||
// Ожидание перед повторным запросом
|
||||
|
||||
if (getCookie('search') !== 'processing') {
|
||||
// На данный момент нет других запросов поиска
|
||||
|
||||
// Запись о существовании запроса
|
||||
search_panel_statusCode_progress();
|
||||
|
||||
// Запрос
|
||||
setTimeout(product_search, data.timer + '000', text, advanced);
|
||||
};
|
||||
};
|
||||
|
||||
// Отображение окна поиска
|
||||
if (data.search_line_window_show === 1) {
|
||||
search_panel_show();
|
||||
|
@ -107,6 +77,75 @@ function search_panel_response(text, advanced, data, status, xhr) {
|
|||
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) {
|
||||
// Получены данные для окна поиска
|
||||
|
||||
if (getCookie('search') !== 'awaiting') {
|
||||
// Удаление цикла
|
||||
clearInterval(getCookie('search'));
|
||||
}
|
||||
|
||||
// Инициализация панели
|
||||
let panel = document.getElementById('search_line_window');
|
||||
|
||||
// Обновление окна результатов поиска
|
||||
panel.innerHTML = data.panel;
|
||||
|
||||
// Инициализация индикатора загрузки
|
||||
let icon = document.getElementById('loading_search');
|
||||
|
||||
// Удаление индикатора загрузки
|
||||
if (icon !== null) icon.remove();
|
||||
|
||||
// Реинициализация
|
||||
reinitialization(panel);
|
||||
} else {
|
||||
// Не получены данные для окна поиска
|
||||
|
||||
if (document.getElementById('loading_search') === null) {
|
||||
// Не найден индикатор загрузки поиска
|
||||
|
||||
// Инициализация панели
|
||||
let panel = document.getElementById('search_line_window');
|
||||
|
||||
// Очистка
|
||||
panel.innerHTML = null;
|
||||
|
||||
// Запись индикатора загрузки
|
||||
panel.appendChild(generate('search', 'Поиск'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
main_response(data, status, xhr);
|
||||
|
@ -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";
|
||||
};
|
||||
|
|
|
@ -38,3 +38,5 @@ function initTextarea(textarea_id, counter_current_id, counter_maximum_id, disab
|
|||
};
|
||||
});
|
||||
};
|
||||
|
||||
document.dispatchEvent(new CustomEvent("textarea.loaded"));
|
||||
|
|