Создание связей, удаление связей и удаление товаров

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-10-04 08:25:22 +10:00
parent 7e63c8be97
commit 43272d051e
11 changed files with 573 additions and 30 deletions

View File

@ -99,6 +99,7 @@ $config = [
'<_key:[0-9]+>/files/<file:[^/]+>' => 'account/file',
'<_key:[0-9]+>/<action:(accept|decline)>' => 'account/<action>',
'product/<catn:[^/]+>' => 'product/index',
'product/<catn:[^/]+>/<action:(connect|disconnect|delete)>' => 'product/<action>',
'<section:(product|cart)>/<catn:[^/]+>/<action:(read|write|edit|delete)>/<target:(title|catn|dscr|dmns|wght|image|cover|comm)>' => '<section>/<action>-<target>',
'profile/geolocation/<action:(init|write)>' => 'profile/geolocation-<action>',
'profile/panel/<panel:(suppliers)>/<block:(requests)>/<action:(search)>' => 'profile/panel-<panel>-<block>-<action>',

View File

@ -12,9 +12,32 @@ use yii\web\HttpException;
use yii\web\UploadedFile;
use app\models\Product;
use app\models\ProductEdgeProduct;
class ProductController extends Controller
{
public function actionIndex(string $catn): array|string|null
{
if ($model = Product::searchByCatn($catn)) {
@ -38,6 +61,321 @@ class ProductController extends Controller
}
}
/**
* Подключение аналога
*
* @param string $catn Артикул
*/
public function actionConnect(string $catn): array|string|null
{
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
}
if ($from = Product::searchByCatn($catn)) {
// Товар найден
if ($target = yii::$app->request->post('catn') ?? yii::$app->request->get('catn')) {
// Инициализирован артикул товара для связи
if ($to = Product::searchByCatn($target)) {
// Существует товар к которому планируется соединение
} else {
// Не существует товар к которому планируется соединение
// Инициализация товара
$to = new Product();
// Запись артикула
$to->catn = (string) $target;
if ($to->save()) {
// Удалось записать товар
} else {
// Не удалось записать товар
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось записать новый товар: $target";
// Переход в конец алгоритма
goto end;
}
}
// Запись ребра
if ($edge = ProductEdgeProduct::writeSafe(Product::collectionName() . "/$from->_key", Product::collectionName() . "/$to->_key", data: ['type' => 'analogue'])) {
// Ребро сохранено
// Запись в журнал о соединении
$from->journal('connect', [
'to' => Product::collectionName() . "/$to->_key"
]);
// Запись в журнал о соединении
$to->journal('connect', [
'from' => Product::collectionName() . "/$from->_key"
]);
// Запись в буфер возврата
$return['alert'] = "Продукты успешно соединены ребром: $edge->_key";
}
}
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар к которому требуется соединение: $catn";
// Переход в конец алгоритма
goto end;
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if (Product::searchByCatn($catn)) {
// Старый товар ещё существует (подразумевается, что произошла ошибка)
// Возврат на страницу товара
return $this->redirect("/product/$catn");
} else {
// Обрабатываемый товар не существует (подразумевается, что произошла ошибка)
// Переадресация на главную страницу
return $this->redirect("/");
}
}
/**
* Отключение аналога
*
* @param string $catn Артикул
*/
public function actionDisconnect(string $catn): array|string|null
{
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
}
if ($from = Product::searchByCatn($catn)) {
// Товар найден
if ($target = yii::$app->request->post('catn') ?? yii::$app->request->get('catn')) {
// Инициализирован артикул товара для связи
if ($to = Product::searchByCatn($target)) {
// Существует товар который нужно отсоединить
// Поиск ребра
if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$from->_key", Product::collectionName() . "/$to->_key", type: 'analogue')[0]) {
// Найдено ребро (from: $from, to: $to)
} else if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$from->_key", type: 'analogue')[0]) {
// Найдено ребро (from: $to, to: $from)
} else {
// Не найдены ребра
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти связь с этим товаром: $target";
// Переход в конец алгоритма
goto end;
}
if ($edge->delete() > 0) {
// Удалось удалить ребро (связь)
// Запись в журнал о разъединении
$from->journal('disconnect', [
'to' => Product::collectionName() . "/$to->_key"
]);
// Запись в журнал о соединении
$to->journal('disconnect', [
'from' => Product::collectionName() . "/$from->_key"
]);
// Запись в буфер возврата
$return['alert'] = "Продукты успешно отсоединены";
} else {
// Не удалось удалить ребро (связь)
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось удалить ребро между $catn и $target";
// Переход в конец алгоритма
goto end;
}
} else {
// Не существует товар который нужно отсоединить
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар который нужно отсоединить: $catn";
// Переход в конец алгоритма
goto end;
}
}
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар от когорого требуется отсоединение: $catn";
// Переход в конец алгоритма
goto end;
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if (Product::searchByCatn($catn)) {
// Обрабатываемый товар ещё существует (подразумевается, что произошла ошибка)
// Возврат на страницу товара
return $this->redirect("/product/$catn");
} else {
// Обрабатываемый товар не существует (подразумевается, что произошла ошибка)
// Переадресация на главную страницу
return $this->redirect("/");
}
}
/**
* Отключение аналога
*
* @param string $catn Артикул
*/
public function actionDelete(string $catn): array|string|null
{
// Инициализация буфера ответа
$return = [
'_csrf' => yii::$app->request->getCsrfToken()
];
if (empty($catn)) {
// Не получен артикул
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Переход в конец алгоритма
goto end;
}
if ($product = Product::searchByCatn($catn)) {
// Товар найден
if ($product->delete() > 0) {
// Удалось удалить
// Запись в буфер возврата
$return['alert'] = "Товар удалён: $catn";
$return['location'] = '/';
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось удалить товар: $catn";
// Переход в конец алгоритма
goto end;
}
} else {
// Запись кода ответа
yii::$app->response->statusCode = 500;
// Запись в буфер возврата
$return['alert'] = "Не удалось найти товар: $catn";
// Переход в конец алгоритма
goto end;
}
/**
* Конец алгоритма
*/
end:
if (yii::$app->request->isPost) {
// POST-запрос
yii::$app->response->format = Response::FORMAT_JSON;
return $return;
}
if (Product::searchByCatn($catn)) {
// Обрабатываемый товар ещё существует (подразумевается, что произошла ошибка)
// Возврат на страницу товара
return $this->redirect("/product/$catn");
} else {
// Обрабатываемый товар не существует (подразумевается, что он успешно удалён)
// Переадресация на главную страницу
return $this->redirect("/");
}
}
public function actionEditTitle(string $catn): array|string|null
{
// Инициализация
@ -45,7 +383,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -95,7 +433,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -152,7 +490,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -202,7 +540,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -258,7 +596,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -308,7 +646,7 @@ class ProductController extends Controller
'_csrf' => yii::$app->request->getCsrfToken()
];
if (is_null($catn)) {
if (empty($catn)) {
// Не получен артикул
yii::$app->response->statusCode = 500;
@ -358,7 +696,7 @@ class ProductController extends Controller
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) {
if (empty($catn) || empty($index)) {
// Не получены обязательные параметры
yii::$app->response->statusCode = 500;
@ -413,7 +751,7 @@ class ProductController extends Controller
];
$index = yii::$app->request->post('index') ?? yii::$app->request->get('index');
if (is_null($catn) || is_null($index)) {
if (empty($catn) || empty($index)) {
// Не получены обязательные параметры
yii::$app->response->statusCode = 500;
@ -427,7 +765,7 @@ class ProductController extends Controller
// Инициализация (буфер нужен из-за кривых сеттеров)
$buffer = $product->imgs;
foreach($buffer as $image_index => &$image) {
foreach ($buffer as $image_index => &$image) {
// Перебор изображений
if ($image_index === (int) $index) {

View File

@ -15,12 +15,26 @@ use app\models\Search;
use app\models\connection\Dellin;
use app\models\Settings;
use Exception;
class SearchController extends Controller
{
/**
* @todo Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
* @todo
* 1. Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
* 2. Пагинация
*/
public function actionIndex(): array|string
{

View File

@ -81,6 +81,14 @@ abstract class Edge extends Document
* Записать (с проверкой на существование)
*
* Создаст ребро только в том случае, если его аналога не существует
*
* @param string $_from Идентификатор отправителя (_id)
* @param string $_from Идентификатор получетеля (_id)
* @param string $type Дополнительное поле - тип взаимосвязи
* @param string $data Дополнительные данные
*
* @todo
* 1. Удалить $type и оставить только $data
*/
public static function writeSafe(string $_from, string $_to, string $type = '', array $data = []): ?static
{
@ -95,6 +103,14 @@ abstract class Edge extends Document
/**
* Записать
*
* @param string $_from Идентификатор отправителя (_id)
* @param string $_from Идентификатор получетеля (_id)
* @param string $type Дополнительное поле - тип взаимосвязи
* @param string $data Дополнительные данные
*
* @todo
* 1. Удалить $type и оставить только $data
*/
public static function write(string $_from, string $_to, string $type, array $data = []): ?static
{

View File

@ -585,4 +585,21 @@ class Product extends Document
select: 'supply_edge_product[0]'
)[0];
}
/**
* Найти все аналоги
*
* @param string $catn Идентификатор товара
* @param int $limit Ограничение по количеству
*
* @return array|null Найденные аналоги
*/
public static function searchAnalogs(string $catn, int $limit = 30): ?array
{
// Поиск ключей аналогов
$analogs = ProductEdgeProduct::searchConnections(self::searchByCatn($catn)->_key, $limit);
return ;
}
}

View File

@ -10,4 +10,49 @@ class ProductEdgeProduct extends Edge
{
return 'product_edge_product';
}
/**
* Найти все соединения
*
* @param string $_key Ключ товара
* @param int $limit Ограничение по количеству
*
* @return array|null Найденные соединения (массив с ключами - "_key")
*/
public static function searchConnections(string $_key, int $limit = 30): ?array
{
// Инициализация буфера возврата
$return = [];
// Поиск аналогов
$edges = self::find()->where(['_from' => Product::collectionName() . "/$_key"])->limit($limit)->all();
foreach ($edges as $edge) {
// Перебор найденных рёбер
// Извлечение ключа
preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches);
// Запись артикула в буфер вывода
$return[] = $matches[1];
}
// Перерасчет ограничения по количеству
$limit -= count($edges);
// Поиск аналогов (с обратной привязкой)
$edges = self::find()->where(['_to' => Product::collectionName() . "/$_key"])->limit($limit)->all();
foreach ($edges as $edge) {
// Перебор найденных рёбер
// Извлечение ключа
preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches);
// Запись артикула в буфер вывода
$return[] = $matches[1];
}
return $return;
}
}

View File

@ -1,7 +1,8 @@
<?php
declare(strict_types=1);
use yii\bootstrap\ActiveForm;
use yii\helpers\Html;
use app\models\Product;
@ -60,7 +61,7 @@ use app\models\Product;
</label>
<?php endif ?>
</div>
<div class="product_slider_image">
<div class="col product_slider_image">
<?php
// Инициализация
@ -93,7 +94,7 @@ use app\models\Product;
<div class="col p-0 unselectable">
<img class="mb-3 img-fluid w-100 rounded" src="$orig"/>
<div class="d-flex justify-content-center">
<button class="flex-grow-1 mr-3 btn button_blue button_clean" onclick="return product_panel_images_cover_write('$catn', '$key');">Сделать основной обложкой</button>
<button class="flex-grow-1 mr-3 btn button_blue button_clean" onclick="return product_panel_images_cover_write('$catn', '$key');">Сделать обложкой</button>
<button class="btn button_red button_clean" onclick="return product_panel_images_delete('$catn', '$key');">Удалить</button>
</div>
</div>
@ -112,14 +113,29 @@ use app\models\Product;
}
?>
</div>
<div class="col ml-4 d-flex flex-column">
<div class="row mb-1">
<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 ?>
<div class="row mb-2">
<?php if (
!yii::$app->user->isGuest
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<h3 id="title_<?= $model['catn'] ?>" class="my-auto pointer-event" role="button" onclick="return product_panel_title_edit('<?= $model['catn'] ?>', this);"><?= $model['name'] ?? 'Без названия' ?></h3>
<h3 id="title_<?= $model['catn'] ?>" class="my-auto pointer-event product_title" role="button" onclick="return product_panel_title_edit('<?= $model['catn'] ?>', this);"><?= $model['name'] ?? 'Без названия' ?></h3>
<?php else : ?>
<h3 id="title_<?= $model['catn'] ?>" class="my-auto"><?= $model['name'] ?? 'Без названия' ?></h3>
<?php endif ?>
@ -131,16 +147,8 @@ use app\models\Product;
&& (yii::$app->user->identity->type === 'administrator'
|| yii::$app->user->identity->type === 'moderator')
) : ?>
<h6 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto pointer-event" role="button" onclick="return product_panel_catn_edit('<?= $model['catn'] ?>', this, true);">
<h6 id="catn_<?= $model['catn'] ?>" class="mr-auto my-auto pointer-event product_title" role="button" onclick="return product_panel_catn_edit('<?= $model['catn'] ?>', this, true);">
<?= $model['catn'] ?? '' ?>
<!-- <small class="d-flex">
<a class="text-dark my-auto ml-3" title="Редактировать" role="button" onclick="return product_panel_edit('<?= $model['catn'] ?>');">
<i class="fas fa-edit"></i>
</a>
<a class="text-dark my-auto ml-2" title="Удалить" role="button" onclick="return product_panel_delete('<?= $model['catn'] ?>');">
<i class="fas fa-trash-alt"></i>
</a>
</small> -->
</h6>
<h6 id="prod_<?= $model['catn'] ?>" class="my-0">
<?= $model['prod'] ?? 'Неизвестно' ?>

View File

@ -1,3 +1,11 @@
<?php
declare(strict_types=1);
use app\models\Product;
?>
<link href="/css/pages/search.css" rel="stylesheet">
<div id="page_search" class="container flex-grow-1 d-flex">
@ -75,6 +83,9 @@
// Инициализация указателя номера цикла
$supply_iterator = 1;
// Инициализация реестра пустышек (товаров без поставок или с ошибками)
$empties = [];
?>
<?php foreach (empty($row['supplies']) || count($row['supplies']) === 0 ? [null] : $row['supplies'] as $supply) : ?>
<?php
@ -134,6 +145,9 @@
</div>
HTML;
// Запись в список ненайденных
$empties[] = $catn;
// Запись блокировщика
$empty_block = true;
@ -212,7 +226,13 @@
?>
<?php endforeach ?>
<?php foreach ($empties as $catn) : ?>
<?php foreach (Product::searchAnalogs($catn) as $product) : ?>
<?php
var_dump($product);
?>
<?php endforeach ?>
<?php endforeach ?>
<div class="col pb-2">
<div class="row p-2 rounded">
<img class="ml-0 h-100 img-fluid rounded" src="<?= $covr ?>" />
@ -229,6 +249,7 @@
</div> -->
<div class="col ml-3 p-0 d-flex flex-column">
<?= $supplies_html ?>
</div>
</div>
</div>

View File

@ -74,3 +74,13 @@
height: 1rem;
border: none;
}
#page_product article .product_title {
word-wrap: anywhere;
word-break: break-all;
}
#page_product article .product_admin_menu {
background-color: #f4f4f6;
}

View File

@ -26,13 +26,27 @@ function main_response(data, status, xhr) {
document.title = data.title;
};
// Перенаправление
// Перенаправление (только запись в историю)
if (data.redirect !== undefined) {
// Перенаправление
// Запись в историю
history.pushState({}, document.title, data.redirect);
};
// Перенаправление
if (data.location !== undefined) {
// Перенаправление
location = data.location;
};
// Системное уведомление
if (data.alert !== undefined) {
// Отображение
alert(data.alert);
};
// CSRF-токен
if (data._csrf !== undefined) {
@ -44,7 +58,7 @@ function main_response(data, status, xhr) {
var page_loaded_for_history = window.history.state;
window.addEventListener('popstate', function() {
window.addEventListener('popstate', function () {
if (page_loaded_for_history) {
window.location.reload();

View File

@ -300,6 +300,65 @@ function product_panel_description_save(catn, element) {
return true;
};
function product_panel_connect(catn) {
if (catn !== null && catn !== undefined) {
$.ajax({
url: '/product/' + catn + '/connect',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'catn': prompt('Подключить аналог')
},
success: product_response_success,
error: product_response_error
});
return false;
};
return true;
}
function product_panel_disconnect(catn) {
if (catn !== null && catn !== undefined) {
$.ajax({
url: '/product/' + catn + '/disconnect',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken(),
'catn': prompt('Отключить аналог')
},
success: product_response_success,
error: product_response_error
});
return false;
};
return true;
}
function product_panel_delete(catn) {
if (catn !== null && catn !== undefined) {
$.ajax({
url: '/product/' + catn + '/delete',
type: 'post',
dataType: 'json',
data: {
'_csrf': yii.getCsrfToken()
},
success: product_response_success,
error: product_response_error
});
return false;
};
return true;
}
function product_panel_images_write(catn, element) {
if (catn !== null && catn !== undefined && element !== null && element !== undefined) {
// Инициализация