feat: Добавлена функциональность управления заказами и улучшен поиск товаров
Заказы: - Добавлены миграции для исторических записей с полями оплаты и получателя - Расширен admin для заказов с инлайнами товаров/комплектов - Реализованы представления списка, создания, редактирования и удаления заказов - Добавлен шаблон подтверждения удаления заказа - Настроены URL-маршруты для работы с заказами Клиенты: - Добавлена миграция с новыми полями адресов и подтверждений - Обновлена модель клиентов с дополнительными полями - Улучшен admin для работы с клиентами Товары: - Значительно улучшен API поиска товаров с поддержкой фильтрации - Добавлен Select2 виджет для динамического поиска товаров - Создан статический JS файл для интеграции Select2 - Оптимизирована обработка запросов и ответов API Прочее: - Добавлены новые настройки в settings.py - Обновлена навигация в navbar.html - Обновлены URL-маршруты проекта 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
148
myproject/products/static/products/js/select2-product-search.js
Normal file
148
myproject/products/static/products/js/select2-product-search.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Select2 Product Search Module
|
||||
* Переиспользуемый модуль для инициализации Select2 с AJAX поиском товаров
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// Форматирование результата в выпадающем списке
|
||||
function formatSelectResult(item) {
|
||||
if (item.loading) return item.text;
|
||||
|
||||
// Если это группа (header для товаров/комплектов), просто возвращаем текст
|
||||
if (item.children) {
|
||||
return item.text;
|
||||
}
|
||||
|
||||
var $container = $('<div class="select2-result-item d-flex justify-content-between align-items-center">');
|
||||
|
||||
// Левая часть: иконка + название
|
||||
var $left = $('<div class="d-flex align-items-center">');
|
||||
|
||||
// Добавляем иконку в зависимости от типа
|
||||
if (item.type === 'product') {
|
||||
$left.append($('<span class="me-2">').text('🌹'));
|
||||
} else if (item.type === 'kit') {
|
||||
$left.append($('<span class="me-2">').text('💐'));
|
||||
} else if (item.type === 'variant') {
|
||||
$left.append($('<span class="me-2">').text('🔄'));
|
||||
}
|
||||
|
||||
// Название
|
||||
var $name = $('<span>').text(item.text);
|
||||
$left.append($name);
|
||||
$container.append($left);
|
||||
|
||||
// Правая часть: цена и статус наличия
|
||||
if (item.actual_price || item.price) {
|
||||
var priceText = item.actual_price || item.price;
|
||||
var $price = $('<span class="text-muted small">').text(priceText + ' руб.');
|
||||
$container.append($price);
|
||||
}
|
||||
|
||||
// Индикатор наличия для товаров
|
||||
if (item.type === 'product' && item.in_stock !== undefined) {
|
||||
if (item.in_stock) {
|
||||
$container.append($('<span class="badge bg-success ms-2 small">').text('В наличии'));
|
||||
} else {
|
||||
$container.append($('<span class="badge bg-secondary ms-2 small">').text('Нет'));
|
||||
}
|
||||
}
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
// Форматирование выбранного элемента
|
||||
function formatSelectSelection(item) {
|
||||
// Добавляем иконку для выбранного элемента
|
||||
if (item.type === 'product') {
|
||||
return '🌹 ' + item.text;
|
||||
} else if (item.type === 'kit') {
|
||||
return '💐 ' + item.text;
|
||||
} else if (item.type === 'variant') {
|
||||
return '🔄 ' + item.text;
|
||||
}
|
||||
return item.text || item.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализирует Select2 для элемента с AJAX поиском товаров
|
||||
* @param {Element|jQuery} element - DOM элемент или jQuery объект select
|
||||
* @param {string} type - Тип поиска ('product', 'variant', 'kit' или 'all')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
* @param {Object} preloadedData - Предзагруженные данные товара
|
||||
*/
|
||||
window.initProductSelect2 = function(element, type, apiUrl, preloadedData) {
|
||||
if (!element) return;
|
||||
|
||||
// Преобразуем в jQuery если нужно
|
||||
var $element = $(element);
|
||||
|
||||
// Если уже инициализирован, пропускаем
|
||||
if ($element.data('select2')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var placeholders = {
|
||||
'product': 'Начните вводить название товара...',
|
||||
'variant': 'Начните вводить название группы...',
|
||||
'kit': 'Начните вводить название комплекта...',
|
||||
'all': 'Начните вводить название товара или комплекта...'
|
||||
};
|
||||
|
||||
var config = {
|
||||
theme: 'bootstrap-5',
|
||||
placeholder: placeholders[type] || 'Выберите...',
|
||||
allowClear: true,
|
||||
language: 'ru',
|
||||
minimumInputLength: 0,
|
||||
ajax: {
|
||||
url: apiUrl,
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function (params) {
|
||||
return {
|
||||
q: params.term || '',
|
||||
type: type,
|
||||
page: params.page || 1
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return {
|
||||
results: data.results,
|
||||
pagination: {
|
||||
more: data.pagination.more
|
||||
}
|
||||
};
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
templateResult: formatSelectResult,
|
||||
templateSelection: formatSelectSelection
|
||||
};
|
||||
|
||||
// Если есть предзагруженные данные, создаем option с ними
|
||||
if (preloadedData) {
|
||||
var option = new Option(preloadedData.text, preloadedData.id, true, true);
|
||||
// Сохраняем дополнительные данные для форматирования
|
||||
$(option).data('data', preloadedData);
|
||||
$element.append(option);
|
||||
}
|
||||
|
||||
$element.select2(config);
|
||||
};
|
||||
|
||||
/**
|
||||
* Инициализирует Select2 для всех элементов с данным селектором
|
||||
* @param {string} selector - CSS селектор элементов
|
||||
* @param {string} type - Тип поиска ('product' или 'variant')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
*/
|
||||
window.initAllProductSelect2 = function(selector, type, apiUrl) {
|
||||
document.querySelectorAll(selector).forEach(function(element) {
|
||||
window.initProductSelect2(element, type, apiUrl);
|
||||
});
|
||||
};
|
||||
|
||||
})(window);
|
||||
@@ -10,11 +10,44 @@
|
||||
function formatSelectResult(item) {
|
||||
if (item.loading) return item.text;
|
||||
|
||||
var $container = $('<div class="select2-result-item">');
|
||||
$container.text(item.text);
|
||||
// Если это группа (header для товаров/комплектов), просто возвращаем текст
|
||||
if (item.children) {
|
||||
return item.text;
|
||||
}
|
||||
|
||||
if (item.price) {
|
||||
$container.append($('<div class="text-muted small">').text(item.price + ' руб.'));
|
||||
var $container = $('<div class="select2-result-item d-flex justify-content-between align-items-center">');
|
||||
|
||||
// Левая часть: иконка + название
|
||||
var $left = $('<div class="d-flex align-items-center">');
|
||||
|
||||
// Добавляем иконку в зависимости от типа
|
||||
if (item.type === 'product') {
|
||||
$left.append($('<span class="me-2">').text('🌹'));
|
||||
} else if (item.type === 'kit') {
|
||||
$left.append($('<span class="me-2">').text('💐'));
|
||||
} else if (item.type === 'variant') {
|
||||
$left.append($('<span class="me-2">').text('🔄'));
|
||||
}
|
||||
|
||||
// Название
|
||||
var $name = $('<span>').text(item.text);
|
||||
$left.append($name);
|
||||
$container.append($left);
|
||||
|
||||
// Правая часть: цена и статус наличия
|
||||
if (item.actual_price || item.price) {
|
||||
var priceText = item.actual_price || item.price;
|
||||
var $price = $('<span class="text-muted small">').text(priceText + ' руб.');
|
||||
$container.append($price);
|
||||
}
|
||||
|
||||
// Индикатор наличия для товаров
|
||||
if (item.type === 'product' && item.in_stock !== undefined) {
|
||||
if (item.in_stock) {
|
||||
$container.append($('<span class="badge bg-success ms-2 small">').text('В наличии'));
|
||||
} else {
|
||||
$container.append($('<span class="badge bg-secondary ms-2 small">').text('Нет'));
|
||||
}
|
||||
}
|
||||
|
||||
return $container;
|
||||
@@ -22,13 +55,21 @@
|
||||
|
||||
// Форматирование выбранного элемента
|
||||
function formatSelectSelection(item) {
|
||||
// Добавляем иконку для выбранного элемента
|
||||
if (item.type === 'product') {
|
||||
return '🌹 ' + item.text;
|
||||
} else if (item.type === 'kit') {
|
||||
return '💐 ' + item.text;
|
||||
} else if (item.type === 'variant') {
|
||||
return '🔄 ' + item.text;
|
||||
}
|
||||
return item.text || item.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализирует Select2 для элемента с AJAX поиском товаров
|
||||
* @param {Element|jQuery} element - DOM элемент или jQuery объект select
|
||||
* @param {string} type - Тип поиска ('product' или 'variant')
|
||||
* @param {string} type - Тип поиска ('product', 'variant', 'kit' или 'all')
|
||||
* @param {string} apiUrl - URL API для поиска
|
||||
* @param {Object} preloadedData - Предзагруженные данные товара
|
||||
*/
|
||||
@@ -45,7 +86,9 @@
|
||||
|
||||
var placeholders = {
|
||||
'product': 'Начните вводить название товара...',
|
||||
'variant': 'Начните вводить название группы...'
|
||||
'variant': 'Начните вводить название группы...',
|
||||
'kit': 'Начните вводить название комплекта...',
|
||||
'all': 'Начните вводить название товара или комплекта...'
|
||||
};
|
||||
|
||||
var config = {
|
||||
@@ -82,6 +125,8 @@
|
||||
// Если есть предзагруженные данные, создаем option с ними
|
||||
if (preloadedData) {
|
||||
var option = new Option(preloadedData.text, preloadedData.id, true, true);
|
||||
// Сохраняем дополнительные данные для форматирования
|
||||
$(option).data('data', preloadedData);
|
||||
$element.append(option);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,54 +5,97 @@ from django.http import JsonResponse
|
||||
from django.db import models
|
||||
from django.core.cache import cache
|
||||
|
||||
from ..models import Product, ProductVariantGroup
|
||||
from ..models import Product, ProductVariantGroup, ProductKit
|
||||
|
||||
|
||||
def search_products_and_variants(request):
|
||||
"""
|
||||
API endpoint для поиска товаров и групп вариантов (совместимость с Select2).
|
||||
Используется для автокомплита при добавлении компонентов в комплект.
|
||||
API endpoint для поиска товаров, групп вариантов и комплектов (совместимость с Select2).
|
||||
Используется для автокомплита при добавлении компонентов в комплект и товаров в заказ.
|
||||
|
||||
Параметры GET:
|
||||
- q: строка поиска (term в Select2)
|
||||
- id: ID товара для получения его данных
|
||||
- type: 'product' или 'variant' (опционально)
|
||||
- id: ID товара/комплекта для получения его данных (формат: "product_123" или "kit_456")
|
||||
- type: 'product', 'variant', 'kit' или 'all' (опционально, по умолчанию 'all')
|
||||
- page: номер страницы для пагинации (по умолчанию 1)
|
||||
|
||||
Возвращает JSON в формате Select2:
|
||||
Возвращает JSON в формате Select2 с группировкой:
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "Роза красная Freedom 50см (PROD-000001)",
|
||||
"sku": "PROD-000001",
|
||||
"price": "150.00",
|
||||
"in_stock": true
|
||||
"text": "Товары",
|
||||
"children": [
|
||||
{
|
||||
"id": "product_1",
|
||||
"text": "Роза красная Freedom 50см (PROD-000001)",
|
||||
"sku": "PROD-000001",
|
||||
"price": "150.00",
|
||||
"actual_price": "135.00",
|
||||
"in_stock": true,
|
||||
"type": "product"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Комплекты",
|
||||
"children": [
|
||||
{
|
||||
"id": "kit_1",
|
||||
"text": "Букет 'Нежность' (KIT-000001)",
|
||||
"sku": "KIT-000001",
|
||||
"price": "2500.00",
|
||||
"actual_price": "2500.00",
|
||||
"type": "kit"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"more": true
|
||||
"more": false
|
||||
}
|
||||
}
|
||||
"""
|
||||
# Если передан ID товара - получаем его данные напрямую
|
||||
product_id = request.GET.get('id', '').strip()
|
||||
if product_id:
|
||||
# Если передан ID товара/комплекта - получаем его данные напрямую
|
||||
item_id = request.GET.get('id', '').strip()
|
||||
if item_id:
|
||||
try:
|
||||
product = Product.objects.get(id=int(product_id), is_active=True)
|
||||
return JsonResponse({
|
||||
'results': [{
|
||||
'id': product.id,
|
||||
'text': f"{product.name} ({product.sku})" if product.sku else product.name,
|
||||
'sku': product.sku,
|
||||
'price': str(product.price) if product.price else None,
|
||||
'actual_price': str(product.actual_price) if product.actual_price else '0',
|
||||
'in_stock': product.in_stock,
|
||||
'type': 'product'
|
||||
}],
|
||||
'pagination': {'more': False}
|
||||
})
|
||||
except (Product.DoesNotExist, ValueError):
|
||||
# Проверяем формат ID: "product_123" или "kit_456" или просто "123"
|
||||
if '_' in item_id:
|
||||
item_type, numeric_id = item_id.split('_', 1)
|
||||
numeric_id = int(numeric_id)
|
||||
else:
|
||||
# Для обратной совместимости: если нет префикса, считаем что это product
|
||||
item_type = 'product'
|
||||
numeric_id = int(item_id)
|
||||
|
||||
if item_type == 'product':
|
||||
product = Product.objects.get(id=numeric_id, is_active=True)
|
||||
return JsonResponse({
|
||||
'results': [{
|
||||
'id': f'product_{product.id}',
|
||||
'text': f"{product.name} ({product.sku})" if product.sku else product.name,
|
||||
'sku': product.sku,
|
||||
'price': str(product.price) if product.price else None,
|
||||
'actual_price': str(product.actual_price) if product.actual_price else '0',
|
||||
'in_stock': product.in_stock,
|
||||
'type': 'product'
|
||||
}],
|
||||
'pagination': {'more': False}
|
||||
})
|
||||
elif item_type == 'kit':
|
||||
kit = ProductKit.objects.get(id=numeric_id, is_active=True)
|
||||
return JsonResponse({
|
||||
'results': [{
|
||||
'id': f'kit_{kit.id}',
|
||||
'text': f"{kit.name} ({kit.sku})" if kit.sku else kit.name,
|
||||
'sku': kit.sku,
|
||||
'price': str(kit.price) if kit.price else None,
|
||||
'actual_price': str(kit.actual_price) if kit.actual_price else '0',
|
||||
'type': 'kit'
|
||||
}],
|
||||
'pagination': {'more': False}
|
||||
})
|
||||
except (Product.DoesNotExist, ProductKit.DoesNotExist, ValueError):
|
||||
return JsonResponse({'results': [], 'pagination': {'more': False}})
|
||||
|
||||
query = request.GET.get('q', '').strip()
|
||||
@@ -62,15 +105,18 @@ def search_products_and_variants(request):
|
||||
|
||||
results = []
|
||||
|
||||
# Если поиска нет - показываем популярные товары
|
||||
# Если поиска нет - показываем популярные товары и комплекты
|
||||
if not query or len(query) < 2:
|
||||
# Кэшируем популярные товары на 1 час
|
||||
cache_key = f'popular_products_{search_type}'
|
||||
cache_key = f'popular_items_{search_type}'
|
||||
cached_results = cache.get(cache_key)
|
||||
|
||||
if cached_results:
|
||||
return JsonResponse(cached_results)
|
||||
|
||||
product_results = []
|
||||
kit_results = []
|
||||
|
||||
if search_type in ['all', 'product']:
|
||||
# Показываем последние добавленные активные товары
|
||||
products = Product.objects.filter(is_active=True)\
|
||||
@@ -85,8 +131,8 @@ def search_products_and_variants(request):
|
||||
# Получаем actual_price: приоритет sale_price > price
|
||||
actual_price = product['sale_price'] if product['sale_price'] else product['price']
|
||||
|
||||
results.append({
|
||||
'id': product['id'],
|
||||
product_results.append({
|
||||
'id': f"product_{product['id']}",
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'price': str(product['price']) if product['price'] else None,
|
||||
@@ -95,6 +141,48 @@ def search_products_and_variants(request):
|
||||
'type': 'product'
|
||||
})
|
||||
|
||||
if search_type in ['all', 'kit']:
|
||||
# Показываем последние добавленные активные комплекты
|
||||
kits = ProductKit.objects.filter(is_active=True)\
|
||||
.order_by('-created_at')[:page_size]\
|
||||
.values('id', 'name', 'sku', 'price', 'sale_price')
|
||||
|
||||
for kit in kits:
|
||||
text = kit['name']
|
||||
if kit['sku']:
|
||||
text += f" ({kit['sku']})"
|
||||
|
||||
# Получаем actual_price: приоритет sale_price > price
|
||||
actual_price = kit['sale_price'] if kit['sale_price'] else kit['price']
|
||||
|
||||
kit_results.append({
|
||||
'id': f"kit_{kit['id']}",
|
||||
'text': text,
|
||||
'sku': kit['sku'],
|
||||
'price': str(kit['price']) if kit['price'] else None,
|
||||
'actual_price': str(actual_price) if actual_price else '0',
|
||||
'type': 'kit'
|
||||
})
|
||||
|
||||
# Формируем результат с группировкой или без
|
||||
if search_type == 'all' and (product_results or kit_results):
|
||||
# С группировкой
|
||||
grouped_results = []
|
||||
if product_results:
|
||||
grouped_results.append({
|
||||
'text': 'Товары',
|
||||
'children': product_results
|
||||
})
|
||||
if kit_results:
|
||||
grouped_results.append({
|
||||
'text': 'Комплекты',
|
||||
'children': kit_results
|
||||
})
|
||||
results = grouped_results
|
||||
else:
|
||||
# Без группировки (когда ищем только product или только kit)
|
||||
results = product_results + kit_results
|
||||
|
||||
response_data = {
|
||||
'results': results,
|
||||
'pagination': {'more': False}
|
||||
@@ -102,14 +190,19 @@ def search_products_and_variants(request):
|
||||
cache.set(cache_key, response_data, 3600)
|
||||
return JsonResponse(response_data)
|
||||
|
||||
# Поиск товаров (регистронезависимый поиск с приоритетом точных совпадений)
|
||||
# Поиск товаров и комплектов (регистронезависимый поиск с приоритетом точных совпадений)
|
||||
product_results = []
|
||||
kit_results = []
|
||||
has_more = False
|
||||
|
||||
# Нормализуем запрос - убираем лишние пробелы
|
||||
query_normalized = ' '.join(query.split())
|
||||
|
||||
from django.db.models import Case, When, IntegerField
|
||||
from django.conf import settings
|
||||
|
||||
# Поиск товаров
|
||||
if search_type in ['all', 'product']:
|
||||
# Нормализуем запрос - убираем лишние пробелы
|
||||
query_normalized = ' '.join(query.split())
|
||||
|
||||
from django.db.models import Case, When, IntegerField
|
||||
from django.conf import settings
|
||||
|
||||
# ВРЕМЕННЫЙ ФИХ для SQLite: удалить когда база данных будет PostgreSQL
|
||||
# SQLite не поддерживает регистронезависимый поиск для кириллицы в LIKE
|
||||
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
||||
@@ -163,8 +256,8 @@ def search_products_and_variants(request):
|
||||
# Получаем actual_price: приоритет sale_price > price
|
||||
actual_price = product['sale_price'] if product['sale_price'] else product['price']
|
||||
|
||||
results.append({
|
||||
'id': product['id'],
|
||||
product_results.append({
|
||||
'id': f"product_{product['id']}",
|
||||
'text': text,
|
||||
'sku': product['sku'],
|
||||
'price': str(product['price']) if product['price'] else None,
|
||||
@@ -174,10 +267,67 @@ def search_products_and_variants(request):
|
||||
})
|
||||
|
||||
has_more = total_products > end
|
||||
else:
|
||||
has_more = False
|
||||
|
||||
# Поиск комплектов
|
||||
if search_type in ['all', 'kit']:
|
||||
# Используем аналогичную логику для комплектов
|
||||
if 'sqlite' in settings.DATABASES['default']['ENGINE']:
|
||||
from django.db.models.functions import Lower
|
||||
query_lower = query_normalized.lower()
|
||||
|
||||
kits_query = ProductKit.objects.annotate(
|
||||
name_lower=Lower('name'),
|
||||
sku_lower=Lower('sku'),
|
||||
description_lower=Lower('description')
|
||||
).filter(
|
||||
models.Q(name_lower__contains=query_lower) |
|
||||
models.Q(sku_lower__contains=query_lower) |
|
||||
models.Q(description_lower__contains=query_lower),
|
||||
is_active=True
|
||||
).annotate(
|
||||
relevance=Case(
|
||||
When(name_lower=query_lower, then=3),
|
||||
When(name_lower__startswith=query_lower, then=2),
|
||||
default=1,
|
||||
output_field=IntegerField()
|
||||
)
|
||||
).order_by('-relevance', 'name')
|
||||
else:
|
||||
kits_query = ProductKit.objects.filter(
|
||||
models.Q(name__icontains=query_normalized) |
|
||||
models.Q(sku__icontains=query_normalized) |
|
||||
models.Q(description__icontains=query_normalized),
|
||||
is_active=True
|
||||
).annotate(
|
||||
relevance=Case(
|
||||
When(name__iexact=query_normalized, then=3),
|
||||
When(name__istartswith=query_normalized, then=2),
|
||||
default=1,
|
||||
output_field=IntegerField()
|
||||
)
|
||||
).order_by('-relevance', 'name')
|
||||
|
||||
kits = kits_query[:page_size].values('id', 'name', 'sku', 'price', 'sale_price')
|
||||
|
||||
for kit in kits:
|
||||
text = kit['name']
|
||||
if kit['sku']:
|
||||
text += f" ({kit['sku']})"
|
||||
|
||||
# Получаем actual_price: приоритет sale_price > price
|
||||
actual_price = kit['sale_price'] if kit['sale_price'] else kit['price']
|
||||
|
||||
kit_results.append({
|
||||
'id': f"kit_{kit['id']}",
|
||||
'text': text,
|
||||
'sku': kit['sku'],
|
||||
'price': str(kit['price']) if kit['price'] else None,
|
||||
'actual_price': str(actual_price) if actual_price else '0',
|
||||
'type': 'kit'
|
||||
})
|
||||
|
||||
# Поиск групп вариантов
|
||||
variant_results = []
|
||||
if search_type in ['all', 'variant']:
|
||||
variants = ProductVariantGroup.objects.filter(
|
||||
models.Q(name__icontains=query) |
|
||||
@@ -186,16 +336,42 @@ def search_products_and_variants(request):
|
||||
|
||||
for variant in variants:
|
||||
count = variant.products.filter(is_active=True).count()
|
||||
results.append({
|
||||
variant_results.append({
|
||||
'id': variant.id,
|
||||
'text': f"{variant.name} ({count} вариантов)",
|
||||
'type': 'variant',
|
||||
'count': count
|
||||
})
|
||||
|
||||
# Формируем финальный результат с группировкой или без
|
||||
# Для 'all' показываем только товары и комплекты (без вариантов)
|
||||
if search_type == 'all':
|
||||
if product_results or kit_results:
|
||||
# С группировкой для заказов (товары + комплекты)
|
||||
grouped_results = []
|
||||
if product_results:
|
||||
grouped_results.append({
|
||||
'text': 'Товары',
|
||||
'children': product_results
|
||||
})
|
||||
if kit_results:
|
||||
grouped_results.append({
|
||||
'text': 'Комплекты',
|
||||
'children': kit_results
|
||||
})
|
||||
final_results = grouped_results
|
||||
else:
|
||||
final_results = []
|
||||
elif search_type == 'variant':
|
||||
# Только варианты
|
||||
final_results = variant_results
|
||||
else:
|
||||
# Без группировки для специфичного поиска (product или kit)
|
||||
final_results = product_results + kit_results + variant_results
|
||||
|
||||
return JsonResponse({
|
||||
'results': results,
|
||||
'pagination': {'more': has_more if search_type == 'product' else False}
|
||||
'results': final_results,
|
||||
'pagination': {'more': has_more}
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user