Доработка блока "Аналогичные товары"

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2021-10-12 13:17:30 +10:00
parent a235ed25ac
commit 53281ee422
7 changed files with 719 additions and 496 deletions

View File

@ -95,12 +95,7 @@ class ProductController extends Controller
// Не существует товар к которому планируется соединение // Не существует товар к которому планируется соединение
// Инициализация товара // Инициализация товара
$to = new Product(); if ($to = Product::writeEmpty((string) $target)) {
// Запись артикула
$to->catn = (string) $target;
if ($to->save()) {
// Удалось записать товар // Удалось записать товар
} else { } else {
// Не удалось записать товар // Не удалось записать товар
@ -116,19 +111,8 @@ class ProductController extends Controller
} }
} }
// Запись ребра if (($edge = $from->synchronization($to)) instanceof ProductEdgeProduct) {
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"; $return['alert'] = "Продукты успешно соединены ребром: $edge->_key";
@ -202,37 +186,9 @@ class ProductController extends Controller
if ($to = Product::searchByCatn($target)) { if ($to = Product::searchByCatn($target)) {
// Существует товар который нужно отсоединить // Существует товар который нужно отсоединить
// Поиск ребра if ($from->disconnect($to)) {
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'] = "Продукты успешно отсоединены"; $return['alert'] = "Продукты успешно отсоединены";
} else { } else {
@ -243,7 +199,7 @@ class ProductController extends Controller
yii::$app->response->statusCode = 500; yii::$app->response->statusCode = 500;
// Запись в буфер возврата // Запись в буфер возврата
$return['alert'] = "Не удалось удалить ребро между $catn и $target"; $return['alert'] = "Не удалось отсоединить $target от $catn";
// Переход в конец алгоритма // Переход в конец алгоритма
goto end; goto end;

View File

@ -8,29 +8,15 @@ use yii;
use yii\web\Controller; use yii\web\Controller;
use yii\web\Response; use yii\web\Response;
use app\models\Account;
use app\models\Product; use app\models\Product;
use app\models\Supply;
use app\models\Search; use app\models\Search;
use app\models\connection\Dellin; /**
use app\models\Settings; * @todo
* 1. Ограничение доступа
use Exception; */
class SearchController extends Controller class SearchController extends Controller
{ {
/** /**
* @todo * @todo
* 1. Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать * 1. Сессию привязать к аккаунту и проверку по нему делать, иначе её можно просто сбрасывать
@ -161,222 +147,26 @@ class SearchController extends Controller
])) { ])) {
// Данные найдены по поиску в полях Каталожного номера // Данные найдены по поиску в полях Каталожного номера
foreach ($response as &$row) { // Генерация данных для представления
// Перебор продуктов $response = Search::content(products: $response);
// Поиск поставок привязанных к продуктам if (yii::$app->request->isPost) {
$connections = Supply::searchByEdge( // POST-запрос
from: 'product',
to: 'supply',
edge: 'supply_edge_product',
limit: 11,
direction: 'OUTBOUND',
subquery_where: [
['product._key' => $row['_key']],
['supply.catn == product.catn'],
['supply_edge_product.type' => 'connect']
],
where: 'supply._id == supply_edge_product[0]._from',
select: '{supply, supply_edge_product}'
);
// Инициализация буфера
$buffer_connections = [];
if (count($connections) === 11) {
// Если в базе данных хранится много поставок
// Инициализация
$row['overload'] = true;
}
foreach ($connections as $key => &$connection) {
// Перебор поставок
if (($cost = $cost['ЦенаЗаЕдиницу'] ?? $connection['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) < 1
|| ($amount = $connection['supply']['amnt'] ?? $connection['supply_edge_product'][0]['onec']['Количество']) < 1
) {
// Цена меньше единицы (подразумевается как ошибка) или количество меньше единицы
// Скрыть из выдачи
unset($connections[$key]);
continue;
}
// Инициализация аккаунта
$connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
// Инициализация продукта
$connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
try {
// Инициализация данных геолокации
try {
$from = (int) $connection['account']['opts']['delivery_from_terminal'] ?? empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
} catch (Exception $e) {
$from = empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
}
try {
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
} catch (Exception $e) {
$to = 36;
}
if (
($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false)
&& time() < $buffer_connection['expires']
) {
// Найдены данные доставки в буфере
// и срок хранения не превышен, информация актуальна
// Запись в буфер вывода
$connection['delivery'] = $buffer_connection['data'];
$connection['delivery']['type'] = 'auto';
} else {
// Инициализация инстанции продукта в базе данных
$product = Product::searchByCatn($connection['product']['catn']);
// Инициализация доставки Dellin (автоматическая)
$product->bffr = [
"$from-$to" => [
'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced(
$from,
$to,
(int) ($connection['product']['wght'] ?? 0),
(int) ($connection['product']['dmns']['x'] ?? 0),
(int) ($connection['product']['dmns']['y'] ?? 0),
(int) ($connection['product']['dmns']['z'] ?? 0)
),
'expires' => time() + 86400
]
] + ($product->bffr ?? []);
$connection['delivery']['type'] = 'auto';
// Отправка в базу данных
$product->update();
}
} catch (Exception $e) {
$connection['delivery']['error'] = true;
// var_dump($e->getMessage());
// var_dump($e->getTrace());
// var_dump($e->getFile());
// var_dump(json_decode($e->getMessage(), true)['errors']);
// die;
}
// Инициализация цены (цена поставки + цена доставки + наша наценка)
$connection['cost'] = $cost + ($connection['delivery']['price']['all'] ?? $connection['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
// Инициализация версии для рассчета доставки по воздуху
$buffer_delivery_avia = $connection;
try {
// Инициализация данных геолокации
if (($cost = $cost['ЦенаЗаЕдиницу'] ?? $buffer_delivery_avia['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) < 1
|| ($amount = $connection['supply']['amnt'] ?? $connection['supply_edge_product'][0]['onec']['Количество']) < 1
) {
// Цена меньше единицы (подразумевается как ошибка) или количество меньше единицы
// Этот код не будет выполняться, так как цена одна на обе позиции и аналогичная проверка выше уже есть
// Однако я это оставлю для возможных доработок в будущем
// Скрыть из выдачи
unset($connections[$key]);
continue;
}
try {
$from = (int) $buffer_delivery_avia['account']['opts']['delivery_from_terminal'] ?? empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
} catch (Exception $e) {
$from = empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
}
try {
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
} catch (Exception $e) {
$to = 36;
}
if (
($buffer_connection = $buffer_delivery_avia['product']['bffr']["$from-$to-avia"] ?? false)
&& time() < $buffer_connection['expires']
) {
// Найдены данные доставки в буфере
// и срок хранения не превышен, информация актуальна
// Запись в буфер вывода
$buffer_delivery_avia['delivery'] = $buffer_connection['data'];
$buffer_delivery_avia['delivery']['type'] = 'avia';
} else {
// Инициализация инстанции продукта в базе данных
$product = Product::searchByCatn($buffer_delivery_avia['product']['catn']);
// Инициализация доставки Dellin (автоматическая)
$product->bffr = [
"$from-$to-avia" => [
'data' => $buffer_delivery_avia['delivery'] = Dellin::calcDeliveryAdvanced(
$from,
$to,
(int) ($buffer_delivery_avia['product']['wght'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['x'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['y'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['z'] ?? 0),
avia: true
),
'expires' => time() + 86400
]
] + ($product->bffr ?? []);
$buffer_delivery_avia['delivery']['type'] = 'avia';
// Отправка в базу данных
$product->update();
}
} catch (Exception $e) {
$buffer_delivery_avia['delivery']['error'] = true;
// echo '<pre>';
// var_dump($e->getMessage());
// var_dump($e->getTrace());
// var_dump($e->getFile());
// var_dump(json_decode($e->getMessage(), true)['errors']);
// die;
}
if (!isset($buffer_delivery_avia['delivery']['error']) || $buffer_delivery_avia['delivery']['error'] !== true) {
// Если рассчиталась доставка самолётом
// Инициализация цены (цена поставки + цена доставки + наша наценка)
$buffer_delivery_avia['cost'] = $cost + ($buffer_delivery_avia['delivery']['price']['all'] ?? $buffer_delivery_avia['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
// Запись в буфер
$buffer_connections[] = $buffer_delivery_avia;
}
}
// Запись обработанных данных
$row['supplies'] = array_merge($connections, $buffer_connections);
}
// Запись ответа
$return = [
'panel' => $this->renderPartial('/search/panel', compact('response')),
'_csrf' => yii::$app->request->getCsrfToken()
];
if ((int) yii::$app->request->post('advanced')) {
// Полноценный поиск
// Запись ответа // Запись ответа
$return['main'] = $this->renderPartial('/search/index', compact('response')); $return = [
$return['hide'] = 1; 'panel' => $this->renderPartial('/search/panel', compact('response', 'query')),
$return['redirect'] = '/search?type=product&q=' . $query; '_csrf' => yii::$app->request->getCsrfToken()
];
if ((int) yii::$app->request->post('advanced')) {
// Полноценный поиск
// Запись ответа
$return['main'] = $this->renderPartial('/search/index', compact('response', 'query'));
$return['hide'] = 1;
$return['redirect'] = '/search?type=product&q=' . $query;
}
} }
} else { } else {
// Данные не найдены // Данные не найдены
@ -413,7 +203,7 @@ class SearchController extends Controller
$advanced = true; $advanced = true;
return $this->render('/search/index', compact('response', 'timer', 'advanced')); return $this->render('/search/index', compact('response', 'timer', 'advanced', 'query'));
} }
} }

View File

@ -112,7 +112,7 @@ abstract class Edge extends Document
* @todo * @todo
* 1. Удалить $type и оставить только $data * 1. Удалить $type и оставить только $data
*/ */
public static function write(string $_from, string $_to, string $type, array $data = []): ?static public static function write(string $_from, string $_to, string $type = '', array $data = []): ?static
{ {
// Инициализация // Инициализация
$edge = new static; $edge = new static;
@ -159,19 +159,31 @@ abstract class Edge extends Document
} }
/** /**
* Поиск рёбер * Поиск рёбер по направлению
*/ */
public static function search(string $target, string $direction = 'OUTBOUND', string $type = '', int $limit = 1): static|array|null public static function search(string $target, string $direction = 'OUTBOUND', string $type = '', int $limit = 1): static|array|null
{ {
if ($direction === 'OUTBOUND') { if (str_contains($direction, 'OUTBOUND')) {
$query = self::find()->where(['_from' => $target, 'type' => $type]); // Исходящие рёбра
} else if ($direction === 'INBOUND') {
$query = self::find()->where(['_to' => $target, 'type' => $type]); $query = static::find()->where(['_from' => $target, 'type' => $type]);
} else if (str_contains($direction, 'INBOUND')) {
// Входящие рёбра
$query = static::find()->where(['_to' => $target, 'type' => $type]);
} else if (str_contains($direction, 'ANY')) {
// Исходящие и входящие рёбра
return static::search(target: $target, direction: 'OUTBOUND', type: $type, limit: $limit) + static::search(target: $target, direction: 'INBOUND', type: $type, limit: $limit);
} }
if ($limit < 2) { if ($limit < 2) {
// Одно ребро или меньше
return $query->one(); return $query->one();
} else { } else {
// Несколько рёбер
return $query->limit($limit)->all(); return $query->limit($limit)->all();
} }
} }

View File

@ -16,6 +16,8 @@ use DateTime;
use DateTimeZone; use DateTimeZone;
use Exception; use Exception;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use stdClass;
/** /**
* Продукт (в ассортименте магазина) * Продукт (в ассортименте магазина)
@ -596,10 +598,183 @@ class Product extends Document
*/ */
public static function searchAnalogs(string $catn, int $limit = 30): ?array public static function searchAnalogs(string $catn, int $limit = 30): ?array
{ {
// Инициализация буфера возврата
$return = [];
// Поиск ключей аналогов // Поиск ключей аналогов
$analogs = ProductEdgeProduct::searchConnections(self::searchByCatn($catn)->_key, $limit); $analogs = ProductEdgeProduct::searchConnections(self::searchByCatn($catn)->_key, $limit);
foreach ($analogs as $analog) {
// Перебор найденных ключей (_key) аналогов
return []; if ($analog = Product::searchById(self::collectionName() . "/$analog")) {
// Найден товар
// Запись в буфер вывода
$return[] = $analog;
} else {
// Не найден товар
}
}
return $return;
}
/**
* Синхронизация аналогов
*
* Связывает с товаром и связанными с ним товарами в одну группу
*
* @param self $to Цель
*
* @return array Созданные рёбра
*/
public function synchronization(self $to): array
{
// Инициализация буфера сохранённых рёбер
$edges = [];
// Инициализация списка товаров в группе и буфера для связей "всех со всеми"
$products = $_products = array_unique(array_merge($this->connections(), $to->connections(), [Product::collectionName() . "/$to->_key"]));
foreach ($products as $product) {
// Перебор связей для создания связей во всех товарах
// Удаление обрабатываемого товара из буферного списка товаров
unset($_products[array_search($product, $_products)]);
foreach ($_products as $_product) {
// Перебор товаров из буфера
if ($from = self::searchById($product)) {
// Товар найден
} else {
if ($from = Product::writeEmpty($product)) {
// Удалось записать товар
} else {
// Не удалось записать товар
continue;
}
}
if ($to = self::searchById($_product)) {
// Товар найден
} else {
if ($to = Product::writeEmpty($_product)) {
// Удалось записать товар
} else {
// Не удалось записать товар
continue;
}
}
if ($edge = $from->connect($to)) {
// Ребро создано
// Запись в буфер
$edges[] = $edge;
}
}
}
return $edges;
}
/**
* Подключение аналога
*
* СВЯЗЫВАЕТ ТОЛЬКО ДВА ТОВАРА, ГРУППА ОСТАНЕТСЯ НЕИЗМЕННОЙ
*
* @param self $to Цель
*/
public function connect(self $to): ?ProductEdgeProduct
{
if (ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", limit: 1)) {
// Дубликат найден
} else if (ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$this->_key", limit: 1)) {
// Дубликат найден (наоборот)
// Вероятно эта проверка здесь не нужна, так как мы знаем входные данные
} else {
// Дубликаты не найдены
if ($edge = ProductEdgeProduct::write(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", data: ['type' => 'analogue'])) {
// Ребро сохранено
// Запись в журнал о соединении
$this->journal('connect analogue', [
'to' => Product::collectionName() . "/$to->_key"
]);
// Запись в журнал о соединении
$to->journal('connect analogue', [
'from' => Product::collectionName() . "/$this->_key"
]);
return $edge;
}
}
return null;
}
/**
* Отключение аналога
*
* @param self $to Цель
*/
public function disconnect(self $to): bool
{
// Поиск ребра
if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", type: 'analogue')[0]) {
// Найдено ребро (from: $this, to: $to)
} else if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$this->_key", type: 'analogue')[0]) {
// Найдено ребро (from: $to, to: $this)
} else {
// Не найдены ребра
return false;
}
if ($edge->delete() > 0) {
// Удалось удалить ребро (связь)
// Запись в журнал о разъединении
$this->journal('disconnect analogue', [
'to' => Product::collectionName() . "/$to->_key"
]);
// Запись в журнал о соединении
$to->journal('disconnect analogue', [
'from' => Product::collectionName() . "/$this->_key"
]);
return true;
}
return false;
}
/**
* Найти все связанные товары
*
* @param int $limit Ограничение по максимальному значению
*/
public function connections(int $limit = 100): array
{
// Инициализация буфера связанных товаров
$products = [];
foreach (ProductEdgeProduct::search(self::collectionName() . "/$this->_key", direction: 'ANY', type: 'analogue', limit: $limit) as $edge) {
// Перебор связей для создания списка товаров (вершин)
// Добавление товаров (вершин) в буфер (подразумевается, что без дубликатов)
if (!in_array($edge->_from, $products, true)) $products[] = $edge->_from;
if (!in_array($edge->_to, $products, true)) $products[] = $edge->_to;
}
return $products;
} }
} }

View File

@ -34,7 +34,7 @@ class ProductEdgeProduct extends Edge
preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches); preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches);
// Запись артикула в буфер вывода // Запись артикула в буфер вывода
$return[] = $matches[1]; $return[] = $matches[1][0];
} }
// Перерасчет ограничения по количеству // Перерасчет ограничения по количеству
@ -47,10 +47,10 @@ class ProductEdgeProduct extends Edge
// Перебор найденных рёбер // Перебор найденных рёбер
// Извлечение ключа // Извлечение ключа
preg_match_all('/\w+\/([0-9]+)/', $edge->_to, $matches); preg_match_all('/\w+\/([0-9]+)/', $edge->_from, $matches);
// Запись артикула в буфер вывода // Запись артикула в буфер вывода
$return[] = $matches[1]; $return[] = $matches[1][0];
} }
return $return; return $return;

View File

@ -5,9 +5,14 @@ declare(strict_types=1);
namespace app\models; namespace app\models;
use yii; use yii;
use yii\web\IdentityInterface;
use app\models\traits\SearchByEdge; use app\models\traits\SearchByEdge;
use app\models\connection\Dellin;
use app\models\Settings;
use datetime;
use exception;
use throwable;
/** /**
* Поиск * Поиск
@ -110,4 +115,435 @@ class Search extends Document
return null; return null;
} }
/**
* Поиск содержимого поиска
*
* @todo В будущем возможно заказ не только поставок реализовать
* Переписать реестр и проверку на дубликаты, не понимаю как они работают
*/
public static function content(array $products, int $limit = 50, int $page = 1): Supply|int|array|null
{
// Инициализация буфера вывода
$response = $products;
// Генерация сдвига по запрашиваемым данным (пагинация)
$offset = $limit * ($page - 1);
foreach ($response as &$row) {
// Перебор продуктов
if ($row instanceof Product) {
// В массиве объект - инстанция товара
// Преобразование к массиву в буфер (унификация данных)
$_row = $row->attributes;
} else {
// В массиве не товар (подразумевается, что это массив с параметрами)
// Запись в буфер (унификация данных)
$_row = $row;
}
// Поиск поставок привязанных к продуктам
$connections = Supply::searchByEdge(
from: 'product',
to: 'supply',
edge: 'supply_edge_product',
limit: $limit,
offset: $offset,
direction: 'OUTBOUND',
subquery_where: [
['product._key' => $_row['_key']],
['supply.catn == product.catn'],
['supply_edge_product.type' => 'connect']
],
where: 'supply._id == supply_edge_product[0]._from',
select: '{supply, supply_edge_product}'
);
// Инициализация буфера
$buffer_connections = [];
if (count($connections) === 11) {
// Если в базе данных хранится много поставок
// Инициализация
$_row['overload'] = true;
}
foreach ($connections as $key => &$connection) {
// Перебор поставок
if (($cost = $cost['ЦенаЗаЕдиницу'] ?? $connection['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) < 1
|| ($amount = $connection['supply']['amnt'] ?? $connection['supply_edge_product'][0]['onec']['Количество']) < 1
) {
// Цена меньше единицы (подразумевается как ошибка) или количество меньше единицы
// Скрыть из выдачи
unset($connections[$key]);
continue;
}
// Инициализация аккаунта
$connection['account'] = Account::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
// Инициализация продукта
$connection['product'] = Product::searchBySupplyId($connection['supply_edge_product'][0]['_from']);
try {
// Инициализация данных геолокации
try {
$from = (int) $connection['account']['opts']['delivery_from_terminal'] ?? empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
} catch (exception $e) {
$from = empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
}
try {
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
} catch (Exception $e) {
$to = 36;
}
if (
($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false)
&& time() < $buffer_connection['expires']
) {
// Найдены данные доставки в буфере
// и срок хранения не превышен, информация актуальна
// Запись в буфер вывода
$connection['delivery'] = $buffer_connection['data'];
$connection['delivery']['type'] = 'auto';
} else {
// Инициализация инстанции продукта в базе данных
$product = Product::searchByCatn($connection['product']['catn']);
// Инициализация доставки Dellin (автоматическая)
$product->bffr = [
"$from-$to" => [
'data' => $connection['delivery'] = Dellin::calcDeliveryAdvanced(
$from,
$to,
(int) ($connection['product']['wght'] ?? 0),
(int) ($connection['product']['dmns']['x'] ?? 0),
(int) ($connection['product']['dmns']['y'] ?? 0),
(int) ($connection['product']['dmns']['z'] ?? 0)
),
'expires' => time() + 86400
]
] + ($product->bffr ?? []);
$connection['delivery']['type'] = 'auto';
// Отправка в базу данных
$product->update();
}
} catch (Exception $e) {
$connection['delivery']['error'] = true;
// var_dump($e->getMessage());
// var_dump($e->getTrace());
// var_dump($e->getFile());
// var_dump(json_decode($e->getMessage(), true)['errors']);
// die;
}
// Инициализация цены (цена поставки + цена доставки + наша наценка)
$connection['cost'] = $cost + ($connection['delivery']['price']['all'] ?? $connection['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
// Инициализация версии для рассчета доставки по воздуху
$buffer_delivery_avia = $connection;
try {
// Инициализация данных геолокации
if (($cost = $cost['ЦенаЗаЕдиницу'] ?? $buffer_delivery_avia['supply_edge_product'][0]['onec']['Цены']['Цена']['ЦенаЗаЕдиницу']) < 1
|| ($amount = $connection['supply']['amnt'] ?? $connection['supply_edge_product'][0]['onec']['Количество']) < 1
) {
// Цена меньше единицы (подразумевается как ошибка) или количество меньше единицы
// Этот код не будет выполняться, так как цена одна на обе позиции и аналогичная проверка выше уже есть
// Однако я это оставлю для возможных доработок в будущем
// Скрыть из выдачи
unset($connections[$key]);
continue;
}
try {
$from = (int) $buffer_delivery_avia['account']['opts']['delivery_from_terminal'] ?? empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
} catch (Exception $e) {
$from = empty(Settings::search()->delivery_from_default) ? 36 : (int) Settings::search()->delivery_from_default;
}
try {
$to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36;
} catch (Exception $e) {
$to = 36;
}
if (
($buffer_connection = $buffer_delivery_avia['product']['bffr']["$from-$to-avia"] ?? false)
&& time() < $buffer_connection['expires']
) {
// Найдены данные доставки в буфере
// и срок хранения не превышен, информация актуальна
// Запись в буфер вывода
$buffer_delivery_avia['delivery'] = $buffer_connection['data'];
$buffer_delivery_avia['delivery']['type'] = 'avia';
} else {
// Инициализация инстанции продукта в базе данных
$product = Product::searchByCatn($buffer_delivery_avia['product']['catn']);
// Инициализация доставки Dellin (автоматическая)
$product->bffr = [
"$from-$to-avia" => [
'data' => $buffer_delivery_avia['delivery'] = Dellin::calcDeliveryAdvanced(
$from,
$to,
(int) ($buffer_delivery_avia['product']['wght'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['x'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['y'] ?? 0),
(int) ($buffer_delivery_avia['product']['dmns']['z'] ?? 0),
avia: true
),
'expires' => time() + 86400
]
] + ($product->bffr ?? []);
$buffer_delivery_avia['delivery']['type'] = 'avia';
// Отправка в базу данных
$product->update();
}
} catch (exception $e) {
$buffer_delivery_avia['delivery']['error'] = true;
// echo '<pre>';
// var_dump($e->getMessage());
// var_dump($e->getTrace());
// var_dump($e->getFile());
// var_dump(json_decode($e->getMessage(), true)['errors']);
// die;
}
if (!isset($buffer_delivery_avia['delivery']['error']) || $buffer_delivery_avia['delivery']['error'] !== true) {
// Если рассчиталась доставка самолётом
// Инициализация цены (цена поставки + цена доставки + наша наценка)
$buffer_delivery_avia['cost'] = $cost + ($buffer_delivery_avia['delivery']['price']['all'] ?? $buffer_delivery_avia['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0);
// Запись в буфер
$buffer_connections[] = $buffer_delivery_avia;
}
}
// Запись обработанных данных
$_row['supplies'] = array_merge($connections, $buffer_connections);
// Запись из буфера
$row = $_row;
}
return $response;
}
/**
* Генерация HTML-кода с найденным товаром
*
* @param array $row Товар сгенерированный через Search::content()
* @param string|null $cpny Компания (сейчас пока что это $name - Название)
* @param string|null $covr Обложка
* @param string|null $catg Категория
* @param string|null $catn Артикул
* @param array $empties Реестр не найденных товаров
*
* @return string HTML-элемент с товаром
*/
public static function generate(array &$row, string|null &$cpny = null, string|null &$covr = null, string|null &$catg = null, string|null &$catn = null, array &$empties = []): string
{
// Инициализация
extract($row);
$cpny ?? $cpny = 'Без названия';
// $dscr ?? $dscr = 'Описание';
$catg ?? $catg = 'Категория';
$catn ?? $catn = 'Неизвестно';
foreach ($imgs ?? [] as $img) {
// Перебор изображений для обложки
if ($img['covr'] ?? false) {
// Обложка найдена
$covr = $img['h150'];
break;
}
}
if (is_null($covr)) {
// Обложка не инициализирована
if (!$covr = $imgs[0]['h150'] ?? false) {
// Не удалось использовать первое изображение как обложку
// Запись обложки по умолчанию
$covr = '/img/covers/h150/product.png';
}
}
// Инициализация буфера с HTML поставок
$supplies_html = '';
// Инициализация блокировщика для пустого блока (на случай, если нет поставок, чтобы не было дубликатов вывода)
$empty_block = false;
// Инициализация счётчика поставок
$supplies_amount = count($row['supplies']);
// Инициализация указателя номера цикла
$supply_iterator = 1;
foreach (empty($row['supplies']) || count($row['supplies']) === 0 ? [null] : $row['supplies'] as $supply) {
// Инициализация модификатора класса
if ($supplies_amount > $supply_iterator) {
// Это не последняя строка с товаром и его поставками
$supply_class_modifier = 'mb-1';
} else {
// Это последняя строка с товаром и его поставками
$supply_class_modifier = '';
}
if (is_null($supply)) {
// Поставки отсутствуют
// Генерация данных об отсутствии заказов
goto no_supplies;
} else {
// Обычная обработка поставки
// Инициализация переменных
extract($supply);
}
// Инициализация цены
$price_raw = $cost;
$price = $price_raw . ' ' . $supply_edge_product[0]['onec']['Цены']['Цена']['Валюта'] ?? 'руб';
// Инициализация количества
$amount_raw = $amount = $supply['amnt'] ?? $supply_edge_product[0]['onec']['Количество'];
if (empty($amount_raw) || $amount_raw < 1) {
// Уже не используется
$amount = 'Под заказ';
} else {
$amount .= ' шт';
}
if ($amount_raw < 1 || $price_raw < 1) {
// Нет в наличии или цена 0 рублей
// Поставки отстутвуют
no_supplies:
// Проверка на блокировку
if ($empty_block) continue;
$supplies_html .= <<<HTML
<div class="row $supply_class_modifier m-0 h-100 text-right">
<a class="col-auto ml-auto my-auto text-dark" href="/order/new/custom" role="button" onclick="return false;">
<small>
Заказать поиск у оператора
</small>
</a>
</div>
HTML;
// Запись в список ненайденных
$empties[] = $catn;
// Запись блокировщика
$empty_block = true;
// Обновление счётчика
++$supply_iterator;
continue;
}
// Инициализация доставки
if (isset($delivery['error']) || $delivery === '?') {
// Не удалось рассчитать доставку
// Инициализация типа доставки
$delivery_type = $delivery['type'] ?? 'auto';
// Инициализация индикатора
$delivery_icon = '<i class="mr-1 fas fa-truck"></i>';
// Инициализация времени
$delivery = '?';
} else {
// Удалось рассчитать доставку
// Инициализация типа доставки
$delivery_type = $delivery['type'] ?? 'auto';
// Инициализация индикатора
$delivery_icon = match ($delivery_type) {
'avia' => '<i class="mr-1 fas fa-plane"></i>',
default => '<i class="mr-1 fas fa-truck"></i>'
};
// Инициализация даты отправки
try {
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
$delivery_send_date = datetime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspSender'])->getTimestamp();
} catch (throwable $e) {
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя)
$delivery_send_date = datetime::createFromFormat('Y-m-d', $delivery['orderDates']['pickup'])->getTimestamp();
}
// Инициализация времени доставки
try {
// Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
$delivery_converted = datetime::createFromFormat('Y-m-d H:i:s', $delivery['orderDates']['giveoutFromOspReceiver'])->getTimestamp();
} catch (Throwable $e) {
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
$delivery_converted = datetime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspReceiver'])->getTimestamp();
}
$delivery = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1;
}
// Инициализация индекса аккаунта
$index = $account['indx'] ?? 'Неизвестен';
// Генерация
$supplies_html .= <<<HTML
<div class="row $supply_class_modifier m-0 text-right">
<small class="ml-auto col-1 ml-2 my-auto pl-2 pr-0">$index</small>
<small class="col-1 my-auto pl-2 pr-0 text-center">$amount</small>
<small class="col-auto mr-2 my-auto pl-2 pr-0 text-left" title="Ориентировочно">$delivery_icon $delivery дн</small>
<b class="col-auto my-auto my-auto text-center">$price</b>
<a class="col-1 ml-0 py-2 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" role="button" onclick="return cart_write('{$supply['_id']}', '$delivery_type');">
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
</a>
</div>
HTML;
// Обновление счётчика
++$supply_iterator;
}
return $supplies_html;
}
} }

View File

@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
use app\models\Product; use app\models\Product;
use app\models\Search;
?> ?>
@ -27,218 +28,29 @@ use app\models\Product;
<?php else : ?> <?php else : ?>
<?php if (isset($response) && is_array($response) && $response) : ?> <?php if (isset($response) && is_array($response) && $response) : ?>
<div class="row py-3 w-100"> <div class="row py-3 w-100 flex-column">
<section class="col"> <section class="col-auto mb-4">
<h4 class="ml-4 mb-3">Товары по запросу: "<?= $query ?? 'ошибка чтения запроса' ?>"</h4>
<?php
// Инициализация
$covr_not_found = true;
$i = 0;
$amount = count($response);
?>
<?php foreach ($response as $row) : ?> <?php foreach ($response as $row) : ?>
<?php <?php
// Инициализация // Инициализация данных товара
extract($row); $cpny = $covr = $catg = $catn = null;
$name ?? $name = 'Без названия';
// $dscr ?? $dscr = 'Описание';
$catg ?? $catg = 'Категория';
$catn ?? $catn = 'Неизвестно';
// Инициализация обложки
$covr = null;
foreach ($imgs ?? [] as $img) {
// Перебор изображений для обложки
if ($img['covr'] ?? false) {
// Обложка найдена
$covr = $img['h150'];
break;
}
}
if (is_null($covr)) {
// Обложка не инициализирована
if (!$covr = $imgs[0]['h150'] ?? false) {
// Не удалось использовать первое изображение как обложку
// Запись обложки по умолчанию
$covr = '/img/covers/h150/product.png';
}
}
// Инициализация буфера с HTML поставок
$supplies_html = '';
// Инициализация блокировщика для пустого блока (на случай, если нет поставок, чтобы не было дубликатов вывода)
$empty_block = false;
// Инициализация счётчика поставок
$supplies_amount = count($row['supplies']);
// Инициализация указателя номера цикла
$supply_iterator = 1;
// Инициализация реестра пустышек (товаров без поставок или с ошибками) // Инициализация реестра пустышек (товаров без поставок или с ошибками)
$empties = []; $empties = [];
// Генерация списка товаров
$supplies_html = Search::generate($row, $cpny, $covr, $catg, $catn, $empties);
extract($row);
?> ?>
<?php foreach (empty($row['supplies']) || count($row['supplies']) === 0 ? [null] : $row['supplies'] as $supply) : ?> <div class="col mb-2">
<?php
// Инициализация модификатора класса
if ($supplies_amount > $supply_iterator) {
// Это не последняя строка с товаром и его поставками
$supply_class_modifier = 'mb-1';
} else {
// Это последняя строка с товаром и его поставками
$supply_class_modifier = '';
}
if (is_null($supply)) {
// Поставки отсутствуют
// Генерация данных об отсутствии заказов
goto no_supplies;
} else {
// Обычная обработка поставки
// Инициализация переменных
extract($supply);
}
// Инициализация цены
$price_raw = $cost;
$price = $price_raw . ' ' . $supply_edge_product[0]['onec']['Цены']['Цена']['Валюта'] ?? 'руб';
// Инициализация количества
$amount_raw = $amount = $supply['amnt'] ?? $supply_edge_product[0]['onec']['Количество'];
if (empty($amount_raw) || $amount_raw < 1) {
// Уже не используется
$amount = 'Под заказ';
} else {
$amount .= ' шт';
}
if ($amount_raw < 1 || $price_raw < 1) {
// Нет в наличии или цена 0 рублей
// Поставки отстутвуют
no_supplies:
// Проверка на блокировку
if ($empty_block) continue;
$supplies_html .= <<<HTML
<div class="row $supply_class_modifier m-0 h-100 text-right">
<a class="col-auto ml-auto my-auto text-dark" href="/order/new/custom" role="button" onclick="return false;">
<small>
Заказать поиск у оператора
</small>
</a>
</div>
HTML;
// Запись в список ненайденных
$empties[] = $catn;
// Запись блокировщика
$empty_block = true;
// Обновление счётчика
++$supply_iterator;
continue;
}
// Инициализация доставки
if (isset($delivery['error']) || $delivery === '?') {
// Не удалось рассчитать доставку
// Инициализация типа доставки
$delivery_type = $delivery['type'] ?? 'auto';
// Инициализация индикатора
$delivery_icon = '<i class="mr-1 fas fa-truck"></i>';
// Инициализация времени
$delivery = '?';
} else {
// Удалось рассчитать доставку
// Инициализация типа доставки
$delivery_type = $delivery['type'] ?? 'auto';
// Инициализация индикатора
$delivery_icon = match ($delivery_type) {
'avia' => '<i class="mr-1 fas fa-plane"></i>',
default => '<i class="mr-1 fas fa-truck"></i>'
};
// Инициализация даты отправки
try {
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspSender'])->getTimestamp();
} catch (Throwable $e) {
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя)
$delivery_send_date = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['pickup'])->getTimestamp();
}
// Инициализация времени доставки
try {
// Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
$delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $delivery['orderDates']['giveoutFromOspReceiver'])->getTimestamp();
} catch (Throwable $e) {
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель)
$delivery_converted = DateTime::createFromFormat('Y-m-d', $delivery['orderDates']['arrivalToOspReceiver'])->getTimestamp();
}
$delivery = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1;
}
// Инициализация индекса аккаунта
$index = $account['indx'] ?? 'Неизвестен';
// Генерация
$supplies_html .= <<<HTML
<div class="row $supply_class_modifier m-0 text-right">
<small class="ml-auto col-1 ml-2 my-auto pl-2 pr-0">$index</small>
<small class="col-1 my-auto pl-2 pr-0 text-center">$amount</small>
<small class="col-auto mr-2 my-auto pl-2 pr-0 text-left" title="Ориентировочно">$delivery_icon $delivery дн</small>
<b class="col-auto my-auto my-auto text-center">$price</b>
<a class="col-1 ml-0 py-2 text-dark d-flex button_white rounded" title="Добавить $catn в корзину" role="button" onclick="return cart_write('{$supply['_id']}', '$delivery_type');">
<i class="fas fa-cart-arrow-down pr-1 m-auto"></i>
</a>
</div>
HTML;
// Обновление счётчика
++$supply_iterator;
?>
<?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"> <div class="row p-2 rounded">
<img class="ml-0 h-100 img-fluid rounded" src="<?= $covr ?>" /> <img class="ml-0 h-100 img-fluid rounded" src="<?= $covr ?>" />
<div class="col-3 ml-3 p-0 d-flex flex-column row_fixed_height"> <div class="col-3 ml-3 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= $catn ?>"> <a class="my-auto text-dark" href="/product/<?= $catn ?>">
<h5 class="m-0"><?= $name ?></h5> <h5 class="m-0"><?= $cpny ?></h5>
<h6 class="m-0"><small><?= $catn ?></small></h6> <h6 class="m-0"><small><?= $catn ?></small></h6>
</a> </a>
</div> </div>
@ -249,14 +61,56 @@ use app\models\Product;
</div> --> </div> -->
<div class="col ml-3 p-0 d-flex flex-column"> <div class="col ml-3 p-0 d-flex flex-column">
<?= $supplies_html ?> <?= $supplies_html ?>
</div> </div>
</div> </div>
</div> </div>
<?php endforeach ?> <?php endforeach ?>
</section> </section>
<?php if (!empty($empties)) : ?>
<section class="col">
<?php foreach ($empties as $catn) : ?>
<?php if ($products = Search::content(products: Product::searchAnalogs($catn))) : ?>
<h4 class="ml-4 mb-3">Аналогичные товары</h4>
<?php foreach ($products as $product) : ?>
<?php
// Инициализация данных товара
$cpny = $covr = $catg = $catn = null;
// Генерация списка товаров
$supplies_html = Search::generate($product, $cpny, $covr, $catg, $catn);
extract($product);
?>
<div class="col mb-2">
<div class="row p-2 rounded">
<img class="ml-0 h-100 img-fluid rounded" src="<?= $covr ?>" />
<div class="col-3 ml-3 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= $catn ?>">
<h5 class="m-0"><?= $cpny ?></h5>
<h6 class="m-0"><small><?= $catn ?></small></h6>
</a>
</div>
<!-- <div class="col-1 ml-2 p-0 d-flex flex-column row_fixed_height">
<a class="my-auto text-dark" href="/product/<?= $catn ?>">
<small><?= $catg ?></small>
</a>
</div> -->
<div class="col ml-3 p-0 d-flex flex-column">
<?= $supplies_html ?>
</div>
</div>
</div>
<?php endforeach ?>
<?php endif ?>
<?php endforeach ?>
</section>
<?php endif ?>
</div> </div>
<?php else : ?> <?php else : ?>