diff --git a/mirzaev/skillparts/system/config/web.php.example b/mirzaev/skillparts/system/config/web.php.example index ab12655..cfe88cc 100644 --- a/mirzaev/skillparts/system/config/web.php.example +++ b/mirzaev/skillparts/system/config/web.php.example @@ -99,6 +99,7 @@ $config = [ '<_key:[0-9]+>/files/' => 'account/file', '<_key:[0-9]+>/' => 'account/', 'product/' => 'product/index', + 'product//' => 'product/', '///' => '
/-', 'profile/geolocation/' => 'profile/geolocation-', 'profile/panel///' => 'profile/panel---', diff --git a/mirzaev/skillparts/system/controllers/ProductController.php b/mirzaev/skillparts/system/controllers/ProductController.php index cb35b7d..932596d 100644 --- a/mirzaev/skillparts/system/controllers/ProductController.php +++ b/mirzaev/skillparts/system/controllers/ProductController.php @@ -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) { diff --git a/mirzaev/skillparts/system/controllers/SearchController.php b/mirzaev/skillparts/system/controllers/SearchController.php index c23dde9..6a24fd5 100644 --- a/mirzaev/skillparts/system/controllers/SearchController.php +++ b/mirzaev/skillparts/system/controllers/SearchController.php @@ -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 { diff --git a/mirzaev/skillparts/system/models/Edge.php b/mirzaev/skillparts/system/models/Edge.php index 067a14c..070a90d 100644 --- a/mirzaev/skillparts/system/models/Edge.php +++ b/mirzaev/skillparts/system/models/Edge.php @@ -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 { diff --git a/mirzaev/skillparts/system/models/Product.php b/mirzaev/skillparts/system/models/Product.php index 8235dd9..2d83270 100644 --- a/mirzaev/skillparts/system/models/Product.php +++ b/mirzaev/skillparts/system/models/Product.php @@ -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 ; + } } diff --git a/mirzaev/skillparts/system/models/ProductEdgeProduct.php b/mirzaev/skillparts/system/models/ProductEdgeProduct.php index d8a94f2..6b77c0f 100644 --- a/mirzaev/skillparts/system/models/ProductEdgeProduct.php +++ b/mirzaev/skillparts/system/models/ProductEdgeProduct.php @@ -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; + } } diff --git a/mirzaev/skillparts/system/views/product/index.php b/mirzaev/skillparts/system/views/product/index.php index 6405980..57b6a81 100644 --- a/mirzaev/skillparts/system/views/product/index.php +++ b/mirzaev/skillparts/system/views/product/index.php @@ -1,7 +1,8 @@ -
+
- +
@@ -112,14 +113,29 @@ use app\models\Product; } ?>
-
-
+
user->isGuest + && yii::$app->user->identity->type === 'administrator' + ) : ?> + + +
user->isGuest && (yii::$app->user->identity->type === 'administrator' || yii::$app->user->identity->type === 'moderator') ) : ?> -

+

@@ -131,16 +147,8 @@ use app\models\Product; && (yii::$app->user->identity->type === 'administrator' || yii::$app->user->identity->type === 'moderator') ) : ?> -
+
-
diff --git a/mirzaev/skillparts/system/views/search/index.php b/mirzaev/skillparts/system/views/search/index.php index b5f4224..8594eed 100644 --- a/mirzaev/skillparts/system/views/search/index.php +++ b/mirzaev/skillparts/system/views/search/index.php @@ -1,3 +1,11 @@ + + diff --git a/mirzaev/skillparts/system/web/css/pages/product.css b/mirzaev/skillparts/system/web/css/pages/product.css index db182fe..1dabd69 100644 --- a/mirzaev/skillparts/system/web/css/pages/product.css +++ b/mirzaev/skillparts/system/web/css/pages/product.css @@ -73,4 +73,14 @@ width: 60px; height: 1rem; border: none; -} \ No newline at end of file +} + +#page_product article .product_title { + word-wrap: anywhere; + word-break: break-all; +} + + +#page_product article .product_admin_menu { + background-color: #f4f4f6; +} diff --git a/mirzaev/skillparts/system/web/js/main.js b/mirzaev/skillparts/system/web/js/main.js index 6e1d717..5e9500a 100644 --- a/mirzaev/skillparts/system/web/js/main.js +++ b/mirzaev/skillparts/system/web/js/main.js @@ -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(); diff --git a/mirzaev/skillparts/system/web/js/product_panel.js b/mirzaev/skillparts/system/web/js/product_panel.js index a55deb7..2abf7d9 100644 --- a/mirzaev/skillparts/system/web/js/product_panel.js +++ b/mirzaev/skillparts/system/web/js/product_panel.js @@ -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) { // Инициализация