feat(products): заменить чекбокс наличия на селект статуса склада в подборе товаров
Заменен чекбокс "только в наличии" на выпадающий список с опциями: все товары, в наличии, не в наличии. Обновлена логика фильтрации в API и интерфейсе.
This commit is contained in:
@@ -60,7 +60,7 @@
|
|||||||
search: '',
|
search: '',
|
||||||
category: '',
|
category: '',
|
||||||
tag: '',
|
tag: '',
|
||||||
inStock: false,
|
stockStatus: 'all',
|
||||||
warehouse: '', // ID склада для фильтрации
|
warehouse: '', // ID склада для фильтрации
|
||||||
skipStockFilter: false // Не фильтровать по остаткам (для приёмки)
|
skipStockFilter: false // Не фильтровать по остаткам (для приёмки)
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
searchClear: c.querySelector('.product-picker-search-clear'),
|
searchClear: c.querySelector('.product-picker-search-clear'),
|
||||||
categorySelect: c.querySelector('.product-picker-category'),
|
categorySelect: c.querySelector('.product-picker-category'),
|
||||||
tagSelect: c.querySelector('.product-picker-tag'),
|
tagSelect: c.querySelector('.product-picker-tag'),
|
||||||
inStockCheckbox: c.querySelector('.product-picker-in-stock'),
|
stockStatusSelect: c.querySelector('.product-picker-stock-status'),
|
||||||
grid: c.querySelector('.product-picker-grid'),
|
grid: c.querySelector('.product-picker-grid'),
|
||||||
loading: c.querySelector('.product-picker-loading'),
|
loading: c.querySelector('.product-picker-loading'),
|
||||||
empty: c.querySelector('.product-picker-empty'),
|
empty: c.querySelector('.product-picker-empty'),
|
||||||
@@ -180,14 +180,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Фильтр по наличию
|
// Фильтр по наличию
|
||||||
if (this.elements.inStockCheckbox) {
|
if (this.elements.stockStatusSelect) {
|
||||||
this.elements.inStockCheckbox.addEventListener('change', function() {
|
this.elements.stockStatusSelect.addEventListener('change', function() {
|
||||||
self.state.filters.inStock = this.checked;
|
self.state.filters.stockStatus = this.value;
|
||||||
self.state.currentPage = 1;
|
self.state.currentPage = 1;
|
||||||
self._loadProducts();
|
self._loadProducts();
|
||||||
});
|
});
|
||||||
// Инициализация из checkbox
|
// Инициализация из select
|
||||||
this.state.filters.inStock = this.elements.inStockCheckbox.checked;
|
this.state.filters.stockStatus = this.elements.stockStatusSelect.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переключатель вида
|
// Переключатель вида
|
||||||
@@ -266,8 +266,8 @@
|
|||||||
if (this.state.filters.tag) {
|
if (this.state.filters.tag) {
|
||||||
params.append('tag', this.state.filters.tag);
|
params.append('tag', this.state.filters.tag);
|
||||||
}
|
}
|
||||||
if (this.state.filters.inStock) {
|
if (this.state.filters.stockStatus !== 'all') {
|
||||||
params.append('in_stock', 'true');
|
params.append('stock_status', this.state.filters.stockStatus);
|
||||||
}
|
}
|
||||||
if (this.state.filters.warehouse) {
|
if (this.state.filters.warehouse) {
|
||||||
params.append('warehouse', this.state.filters.warehouse);
|
params.append('warehouse', this.state.filters.warehouse);
|
||||||
|
|||||||
@@ -90,16 +90,11 @@ ProductSearchPicker.init('#writeoff-products', {
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Фильтр по наличию -->
|
<!-- Фильтр по наличию -->
|
||||||
<div class="form-check form-switch">
|
<select class="form-select form-select-sm product-picker-stock-status" style="width: auto;">
|
||||||
<input class="form-check-input product-picker-in-stock"
|
<option value="all" {% if not filter_in_stock_only|default:False %}selected{% endif %}>Все товары</option>
|
||||||
type="checkbox"
|
<option value="in_stock" {% if filter_in_stock_only|default:False %}selected{% endif %}>В наличии</option>
|
||||||
id="{{ container_id|default:'product-search-picker' }}-in-stock"
|
<option value="out_of_stock">Не в наличии</option>
|
||||||
{% if filter_in_stock_only|default:False %}checked{% endif %}>
|
</select>
|
||||||
<label class="form-check-label small"
|
|
||||||
for="{{ container_id|default:'product-search-picker' }}-in-stock">
|
|
||||||
Только в наличии
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if show_view_toggle|default:True %}
|
{% if show_view_toggle|default:True %}
|
||||||
<!-- Переключатель вида -->
|
<!-- Переключатель вида -->
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ def _get_product_photo_url(product_id):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _apply_product_filters(queryset, category_id=None, tag_id=None, in_stock_only=False, warehouse_id=None, skip_stock_filter=False):
|
def _apply_product_filters(queryset, category_id=None, tag_id=None, stock_status='all', warehouse_id=None, skip_stock_filter=False):
|
||||||
"""Применяет фильтры к queryset товаров.
|
"""Применяет фильтры к queryset товаров.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
stock_status: 'all' - все товары, 'in_stock' - только в наличии, 'out_of_stock' - только не в наличии
|
||||||
skip_stock_filter: Если True, warehouse_id не фильтрует по остаткам.
|
skip_stock_filter: Если True, warehouse_id не фильтрует по остаткам.
|
||||||
Используется для приёмки товаров.
|
Используется для приёмки товаров.
|
||||||
"""
|
"""
|
||||||
@@ -32,8 +33,10 @@ def _apply_product_filters(queryset, category_id=None, tag_id=None, in_stock_onl
|
|||||||
queryset = queryset.filter(categories__id=category_id)
|
queryset = queryset.filter(categories__id=category_id)
|
||||||
if tag_id:
|
if tag_id:
|
||||||
queryset = queryset.filter(tags__id=tag_id)
|
queryset = queryset.filter(tags__id=tag_id)
|
||||||
if in_stock_only:
|
if stock_status == 'in_stock':
|
||||||
queryset = queryset.filter(in_stock=True)
|
queryset = queryset.filter(in_stock=True)
|
||||||
|
elif stock_status == 'out_of_stock':
|
||||||
|
queryset = queryset.filter(in_stock=False)
|
||||||
if warehouse_id and not skip_stock_filter:
|
if warehouse_id and not skip_stock_filter:
|
||||||
# Фильтруем только товары, которые есть на указанном складе с доступным количеством
|
# Фильтруем только товары, которые есть на указанном складе с доступным количеством
|
||||||
# НЕ применяется при skip_stock_filter=True (приёмка товаров)
|
# НЕ применяется при skip_stock_filter=True (приёмка товаров)
|
||||||
@@ -58,7 +61,7 @@ def search_products_and_variants(request):
|
|||||||
- page: номер страницы для пагинации (по умолчанию 1)
|
- page: номер страницы для пагинации (по умолчанию 1)
|
||||||
- category: ID категории для фильтрации (опционально)
|
- category: ID категории для фильтрации (опционально)
|
||||||
- tag: ID тега для фильтрации (опционально)
|
- tag: ID тега для фильтрации (опционально)
|
||||||
- in_stock: 'true' для фильтрации только товаров в наличии (опционально)
|
- stock_status: 'all' (все), 'in_stock' (в наличии), 'out_of_stock' (не в наличии) (опционально, по умолчанию 'all')
|
||||||
- warehouse: ID склада для фильтрации только товаров с доступным остатком (опционально)
|
- warehouse: ID склада для фильтрации только товаров с доступным остатком (опционально)
|
||||||
|
|
||||||
Возвращает JSON в формате Select2 с группировкой:
|
Возвращает JSON в формате Select2 с группировкой:
|
||||||
@@ -190,14 +193,14 @@ def search_products_and_variants(request):
|
|||||||
# Дополнительные фильтры
|
# Дополнительные фильтры
|
||||||
category_id = request.GET.get('category', '').strip()
|
category_id = request.GET.get('category', '').strip()
|
||||||
tag_id = request.GET.get('tag', '').strip()
|
tag_id = request.GET.get('tag', '').strip()
|
||||||
in_stock_only = request.GET.get('in_stock', '').lower() == 'true'
|
stock_status = request.GET.get('stock_status', 'all').strip()
|
||||||
warehouse_id = request.GET.get('warehouse', '').strip()
|
warehouse_id = request.GET.get('warehouse', '').strip()
|
||||||
skip_stock_filter = request.GET.get('skip_stock_filter', '').lower() == 'true'
|
skip_stock_filter = request.GET.get('skip_stock_filter', '').lower() == 'true'
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
# Проверяем, есть ли дополнительные фильтры
|
# Проверяем, есть ли дополнительные фильтры
|
||||||
has_filters = category_id or tag_id or in_stock_only or warehouse_id
|
has_filters = category_id or tag_id or stock_status != 'all' or warehouse_id
|
||||||
|
|
||||||
# Если поиска нет - показываем популярные товары и комплекты
|
# Если поиска нет - показываем популярные товары и комплекты
|
||||||
if not query or len(query) < 2:
|
if not query or len(query) < 2:
|
||||||
@@ -215,7 +218,7 @@ def search_products_and_variants(request):
|
|||||||
# Показываем последние добавленные активные товары
|
# Показываем последние добавленные активные товары
|
||||||
products_qs = Product.objects.filter(status='active').prefetch_related('sales_units__unit')
|
products_qs = Product.objects.filter(status='active').prefetch_related('sales_units__unit')
|
||||||
# Применяем фильтры
|
# Применяем фильтры
|
||||||
products_qs = _apply_product_filters(products_qs, category_id, tag_id, in_stock_only, warehouse_id, skip_stock_filter)
|
products_qs = _apply_product_filters(products_qs, category_id, tag_id, stock_status, warehouse_id, skip_stock_filter)
|
||||||
products = products_qs.order_by('-created_at')[:page_size]
|
products = products_qs.order_by('-created_at')[:page_size]
|
||||||
|
|
||||||
for product in products:
|
for product in products:
|
||||||
@@ -359,7 +362,7 @@ def search_products_and_variants(request):
|
|||||||
).order_by('-relevance', 'name')
|
).order_by('-relevance', 'name')
|
||||||
|
|
||||||
# Применяем дополнительные фильтры
|
# Применяем дополнительные фильтры
|
||||||
products_query = _apply_product_filters(products_query, category_id, tag_id, in_stock_only, warehouse_id, skip_stock_filter)
|
products_query = _apply_product_filters(products_query, category_id, tag_id, stock_status, warehouse_id, skip_stock_filter)
|
||||||
|
|
||||||
# Добавляем prefetch для единиц продажи
|
# Добавляем prefetch для единиц продажи
|
||||||
products_query = products_query.prefetch_related('sales_units__unit')
|
products_query = products_query.prefetch_related('sales_units__unit')
|
||||||
|
|||||||
Reference in New Issue
Block a user