Активные и неактивные товары

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-02-07 14:29:18 +10:00
parent a971aef392
commit 4c91c3192a
13 changed files with 443 additions and 105 deletions

View File

@ -589,21 +589,27 @@ class OrderController extends Controller
$model = Order::searchByType(supplies: true);
// Поиск ребра
$edge = AccountEdgeOrder::searchByVertex(yii::$app->user->id, $model->readId(), 'current');
$account_edge_order = AccountEdgeOrder::searchByVertex(yii::$app->user->id, $model->readId(), 'current')[0];
if (count($edge) > 1) {
if ($supplies = OrderEdgeSupply::searchByDirection($account_edge_order->to)) {
// Поставки найдены
// Добавить проверку на то, что товары активны
}
var_dump($supplies); die;
if (count($account_edge_order) > 1) {
// Найден более чем 1 заказ
return null;
}
// Инициализация
$edge = $edge[0];
// Запись
$edge->type = 'requested';
$account_edge_order->type = 'requested';
if ($edge->update()) {
if ($account_edge_order->update()) {
// Удалось сохранить изменения
// Запись в журнал
@ -613,6 +619,8 @@ class OrderController extends Controller
$supplies = [];
foreach ($model['supplies'] as $supply) {
// Перебор поставок
$supplies[] = [
'title' => $supply['supply']['catn'],
'amount' => [
@ -634,13 +642,13 @@ class OrderController extends Controller
],
'order' => [
'id' => $model->_key,
'date' => $edge->date ?? time(),
'date' => $account_edge_order->date ?? time(),
'entries' => $supplies
]
]));
// Отправка уведомлений модераторам
Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $edge->_key]), true, '@auth', Notification::TYPE_MODERATOR_ORDER_NEW);
Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $account_edge_order->_key]), true, '@auth', Notification::TYPE_MODERATOR_ORDER_NEW);
}
return $this->actionIndex();

View File

@ -5,18 +5,82 @@ declare(strict_types=1);
namespace app\controllers;
use yii;
use yii\filters\AccessControl;
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 ProductController extends Controller
{
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'actions' => [
'index',
]
],
[
'allow' => true,
'roles' => ['@'],
'actions' => []
],
[
'allow' => true,
'actions' => [
'read',
'write',
'delete',
'connect',
'disconnect',
'edit-title',
'edit-catn',
'edit-dscr',
'edit-dmns',
'edit-wght',
'write-image',
'delete-image',
'write-cover',
'write-image',
'status'
],
'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']
]
]
]
];
}
public function actionIndex(string $catn): array|string|null
{
if ($model = Product::searchByCatn($catn)) {
@ -143,6 +207,44 @@ class ProductController extends Controller
return $this->redirect("/");
}
/**
* Чтение товаров
*
* @param string $stts Статус
*
* @return string|array|null
*/
public function actionRead(string $stts = 'all'): string|array|null
{
if (yii::$app->request->isPost) {
// POST-запрос
// Инициализация входных параметров
$amount = yii::$app->request->post('amount') ?? yii::$app->request->get('amount') ?? 50;
$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);
// Запись формата ответа
yii::$app->response->format = Response::FORMAT_JSON;
return [
'products' => $this->renderPartial('/product/list', compact('account', 'products')),
'_csrf' => yii::$app->request->getCsrfToken()
];
}
return false;
}
/**
* Удаление товара
*
@ -307,7 +409,7 @@ class ProductController extends Controller
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар к которому требуется соединение: $catn";
$return['alert'] = "Не удалось найти товар: $catn";
// Переход в конец алгоритма
goto end;
@ -848,4 +950,81 @@ class ProductController extends Controller
return $this->redirect('/');
}
}
/**
* Изменение статуса
*
* @param string $catn Артикул
*/
public function actionStatus(string $catn): array|string|null
{
if (yii::$app->request->isPost) {
// POST-запрос
// Запись заголовка с форматом ответа
yii::$app->response->format = Response::FORMAT_JSON;
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Найден товар
// Запись старого статуса
$old = $product->stts;
// Запись параметров
$product->stts = $stts = yii::$app->request->post('stts') ?? 'inactive';
if ($product->update() > 0) {
// Записаны изменения
// Запись в журнал
$product->journal(data: [
'stts' => [
'old' => $old,
'new' => $stts
]
]);
$return['main'] = $this->renderPartial('index', ['model' => $product]);
} else {
// Не записаны изменения
// Отправка уведомления
Notification::_write("Не удалось изменить статус на $stts у товара $catn", type: Notification::TYPE_ERROR);
}
} else {
// Не найден товар
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Отправка уведомления
Notification::_write("Не удалось найти товар $catn", type: Notification::TYPE_ERROR);
// Переход в конец алгоритма
goto end;
}
// Конец алгоритма
end:
return $return;
}
return null;
}
}

View File

@ -134,7 +134,7 @@ class SearchController extends Controller
$limit = yii::$app->request->isPost ? 10 : 20;
if ($response = Product::searchByPartialCatn($query, $limit, [
if ($response = Product::searchByPartialCatn($query, 'active', $limit, [
'_key' => '_key',
'catn' => 'catn',
'prod' => 'prod',

View File

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

View File

@ -89,6 +89,7 @@ class Product extends Document
'imgs',
'time',
'bffr',
'stts'
]
);
}
@ -109,6 +110,7 @@ class Product extends Document
'imgs' => 'Изображения (imgs)',
'time' => 'Срок доставки (time)',
'bffr' => 'Буфер',
'stts' => 'Статус',
'file_excel' => 'Документ (file_excel)',
'file_image' => 'Изображение (file_image)',
'group' => 'Группа (group)',
@ -168,6 +170,12 @@ class Product extends Document
'max' => 30000,
'message' => '{attribute} должен иметь значение от 0 до 30000'
],
[
'stts',
'string',
'length' => [4, 20],
'message' => '{attribute} должен быть строкой от 4 до 20 символов'
],
[
'file_excel',
'file',
@ -201,6 +209,29 @@ class Product extends Document
);
}
/**
* Перед сохранением
*
* @todo Подождать обновление от ебаного Yii2 и добавить
* проверку типов передаваемых параметров
*/
public function beforeSave($create): bool
{
if (parent::beforeSave($create)) {
// Пройдена родительская проверка
if ($this->isNewRecord) {
// Новая запись
$this->stts = 'inactive';
}
return true;
}
return false;
}
/**
* Запись пустого продукта
*/
@ -346,11 +377,12 @@ class Product extends Document
*
* @todo Переделать нормально
*/
public static function searchByPartialCatn(string $catn, int $limit = 1, array $select = []): static|array|null
public static function searchByPartialCatn(string $catn, string $stts = 'active', int $limit = 1, array $select = []): static|array|null
{
$query = self::find()
->for('product')
->in('product_search')
->where(['stts' => $stts])
->filter(['catn' => $catn], 'START_SENSETIVE')
->limit($limit)
->orderBy(['catn' => 'ASC'])

View File

@ -118,7 +118,7 @@ class Search extends Document
/**
* Поиск содержимого поиска
* Поиск содержимого поиска (продуктов)
*
* @todo В будущем возможно заказ не только поставок реализовать
* Переписать реестр и проверку на дубликаты, не понимаю как они работают
@ -343,6 +343,7 @@ class Search extends Document
}
}
// Запись обработанных данных
$_row['supplies'] = array_merge($connections, $buffer_connections);

View File

@ -459,7 +459,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
$analogs = explode(',', $row['Артикул'] ?? $row['артикул'] ?? $row['Article'] ?? $row['article'] ?? $row['catn'], 50);
// Инициализация функции создания поставки
$create = function (string $_supply) use ($row, $analogs, &$created, &$updated, &$imported, $amount, $account) {
$create = function (string $_supply) use ($row, $analogs, &$created, &$updated, &$imported, $amount, $account): bool {
// Очистка
$_supply = trim($_supply);
@ -591,21 +591,35 @@ class Supply extends Product implements ProductInterface, OfferInterface
if ($product = Product::searchByCatn($supply->catn)) {
// Найден товар подходящий для привязки с только что созданной поставкой
if (isset($product->prod) && $product->prod === $supply->prod) {
// Производитель совпадает с тем, что указан в товаре
for ($i = 0; $i++ < $amount;) {
// Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии)
// Запись ребра (с проверкой на дубликат)
SupplyEdgeProduct::writeSafe($supply->readId(), $product->readId(), data: ['type' => 'connect']);
}
}
// Приведение типа (для анализатора)
if (is_array($product)) $product = $product[0];
} else {
// Не найден товар подходящий для привязки с только что созданной поставкой
// Отправка уведомления
Notification::_write("Не найден товар подходящий для связи с поставкой: $supply->catn", account: '@authorized');
if ($product = Product::writeEmpty($supply->catn)) {
// Удалось записать новый товар (НЕАКТИВНЫЙ)
// Отправка уведомления
// Notification::_write("Не найден товар подходящий для связи с поставкой: $supply->catn", account: '@authorized');
} else {
// Не удалось записать новый товар
// Отправка уведомления
Notification::_write("Не удалось создать новый товар: $supply->catn", account: '@authorized');
return false;
}
}
if (isset($product->prod) && $product->prod === $supply->prod) {
// Производитель совпадает с тем, что указан в товаре
for ($i = 0; $i++ < $amount;) {
// Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии)
// Запись ребра (с проверкой на дубликат)
SupplyEdgeProduct::writeSafe($supply->readId(), $product->readId(), data: ['type' => 'connect']);
}
}
} else {
// Проверка не пройдена

View File

@ -87,6 +87,7 @@ use app\models\Product;
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) {
// Инициализация артикула
$catn = $model['catn'];
echo <<<HTML
@ -114,21 +115,21 @@ 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'
) : ?>
<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'] ?>');">
<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'] ?>');">
<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'] ?>');">
<i class="fas fa-trash-alt my-auto"></i>
</a>
</nav>
<?php endif ?>
!yii::$app->user->isGuest
&& yii::$app->user->identity->type === 'administrator'
) : ?>
<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'] ?>');">
<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'] ?>');">
<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'] ?>');">
<i class="fas fa-trash-alt my-auto"></i>
</a>
</nav>
<?php endif ?>
<div class="row mb-2">
<?php if (
!yii::$app->user->isGuest
@ -219,24 +220,22 @@ use app\models\Product;
?>
</p>
</div>
<?php
$form = ActiveForm::begin([
'id' => 'form_product_cart',
'action' => false,
'fieldConfig' => [
'template' => '{input}',
],
'options' => [
'onsubmit' => 'return false;',
'class' => 'row mt-auto'
]
]);
if (isset($model['stts']) && $model['stts'] === 'active') {
// Товар активен
// Просто для теста
$model = new Product();
echo <<<HTML
<button class="row btn button_red button_clean" onclick="return product_panel_product_stts('$catn', 'inactive');">Деактивировать</button>
HTML;
} else {
// Товар неактивен, либо что-то с ним ещё
echo <<<HTML
<button class="row btn button_green button_clean" onclick="return product_panel_product_stts('$catn', 'active');">Активировать</button>
HTML;
}
?>
<?php ActiveForm::end(); ?>
</div>
</div>

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
use app\models\Settings;
use DateTime;
use DateTimeZone;
// Инициализация счетчика аккаунтов
$amount = 0;
// Инициализация часового пояса
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0];
?>
<?php foreach ($products as $product) : ?>
<?php
foreach ($product->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"><?= ++$amount ?>.</div>
<a class="pr-0 col overflow-hidden" title="<?= $product->name ?? 'Открыть страницу товара' ?>" href="/product/<?= $product->catn ?? 'Неизвестно' ?>">
<?= $product->catn ?? 'Неизвестно' ?>
</a>
<div class="my-auto pr-0 col-auto text-right">
<?= $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($products)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>

View File

@ -159,47 +159,13 @@ $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>
<div class="dropdown-divider mb-4"></div>
<?php
// Инициализация счетчика аккаунтов
$amount = 0;
// Чтение аккаунтов
$products = Product::read(limit: 100, order: ['desc']);
?>
<?php foreach ($products ?? [] as $product) : ?>
<?php
foreach ($product->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"><?= ++$amount ?>.</div>
<a class="pr-0 col overflow-hidden" title="<?= $product->name ?? 'Открыть страницу товара' ?>" href="/product/<?= $product->catn ?? 'Неизвестно' ?>">
<?= $product->catn ?? 'Неизвестно' ?>
</a>
<div class="my-auto pr-0 col-auto text-right">
<?= $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($products)) : ?>
<div class="dropdown-divider mb-3"></div>
<?php endif ?>
<?php endforeach ?>
<div id="profile_panel_input_products_wrap"></div>
</div>
<input type="radio" id="profile_panel_input_supplies" name="main_panel" <?= $panel === 'profile_panel_input_supplies' ? 'checked' : null ?> />

View File

@ -178,6 +178,25 @@ main {
transition: 0s;
}
.button_green {
color: #eee;
background-color: #00c431;
transition: 0s;
}
.button_green:hover {
color: #fff;
background-color: #04d639;
transition: 0s;
}
.button_green:active,
.button_green:focus {
color: #ddd;
background-color: #01ae2c;
transition: 0s;
}
.button_white,
.button_white_small {
background-color: #fff;

View File

@ -437,7 +437,7 @@ function product_panel_images_cover_write(catn, index) {
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'index': index
index
},
success: product_response_success,
error: product_response_error
@ -461,3 +461,23 @@ function product_panel_handler_save(save, catn, element, ...vars) {
document.body.addEventListener('click', product_panel_handler_save_function, true);
};
};
function product_panel_product_stts(catn, stts) {
if (catn !== null && catn !== undefined && stts !== null && stts !== undefined) {
$.ajax({
url: '/product/' + catn + '/status',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
stts
},
success: product_response_success,
error: product_response_error
});
return false;
};
return true;
};

View File

@ -72,23 +72,32 @@ 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');
// Активация запрошенной вкладки
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);
}
// Инициализация запрошенной вкладки
document.getElementById(button).checked = true;
document.getElementById(button).removeAttribute('onclick');
document.querySelector('[for="' + button + '"]').classList.add('active');
}
function page_profile_panel_accounts_choose(button = 'profile_panel_input_accounts_control', active = false) {
@ -729,3 +738,44 @@ function profile_panel_input_suppliers_accounts_create_product(button, _key) {
return false;
}
// Прочитать товары
function profile_panel_products_read(search = '@last', type = '@last', from = '@last', to = '@last') {
// '@last' - оставить без изменений и взять данные из cookie
if (search.length > 1) {
// Проверка на длину запроса пройдена
// Инициализация буфера с данными запроса
let data = {
'_csrf': yii.getCsrfToken(),
search,
from,
to
};
// Запрос
$.ajax({
url: '/products/read/' + type,
type: 'post',
dataType: 'json',
data: data,
success: (data, status, xhr) => {
// Инициализация оболочки
let element = document.getElementById('profile_panel_input_products_wrap');
if (data.products !== undefined && element !== null) {
// Передан список продуктов и найден элемент-оболочка продуктов
// Запись полученного содержимого
element.innerHTML = data.products;
}
return page_profile_response_success(data, status, xhr);
},
error: page_profile_response_error
});
};
return false;
}