From 666e007931142e22c192276c0969d1ad3ebb7a35 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sun, 4 Jan 2026 19:41:28 +0300 Subject: [PATCH] =?UTF-8?q?feat(products):=20=D0=B7=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D1=8C=20=D1=87=D0=B5=D0=BA=D0=B1=D0=BE=D0=BA?= =?UTF-8?q?=D1=81=20=D0=BD=D0=B0=D0=BB=D0=B8=D1=87=D0=B8=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B5=D0=BB=D0=B5=D0=BA=D1=82=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D1=83=D1=81=D0=B0=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D0=BF=D0=BE=D0=B4=D0=B1=D0=BE=D1=80=D0=B5=20?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Заменен чекбокс "только в наличии" на выпадающий список с опциями: все товары, в наличии, не в наличии. Обновлена логика фильтрации в API и интерфейсе. --- .../products/js/product-search-picker.js | 18 +++++++++--------- .../components/product_search_picker.html | 15 +++++---------- myproject/products/views/api_views.py | 17 ++++++++++------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/myproject/products/static/products/js/product-search-picker.js b/myproject/products/static/products/js/product-search-picker.js index ad9e70d..5b12a97 100644 --- a/myproject/products/static/products/js/product-search-picker.js +++ b/myproject/products/static/products/js/product-search-picker.js @@ -60,7 +60,7 @@ search: '', category: '', tag: '', - inStock: false, + stockStatus: 'all', warehouse: '', // ID склада для фильтрации skipStockFilter: false // Не фильтровать по остаткам (для приёмки) } @@ -86,7 +86,7 @@ searchClear: c.querySelector('.product-picker-search-clear'), categorySelect: c.querySelector('.product-picker-category'), 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'), loading: c.querySelector('.product-picker-loading'), empty: c.querySelector('.product-picker-empty'), @@ -180,14 +180,14 @@ } // Фильтр по наличию - if (this.elements.inStockCheckbox) { - this.elements.inStockCheckbox.addEventListener('change', function() { - self.state.filters.inStock = this.checked; + if (this.elements.stockStatusSelect) { + this.elements.stockStatusSelect.addEventListener('change', function() { + self.state.filters.stockStatus = this.value; self.state.currentPage = 1; self._loadProducts(); }); - // Инициализация из checkbox - this.state.filters.inStock = this.elements.inStockCheckbox.checked; + // Инициализация из select + this.state.filters.stockStatus = this.elements.stockStatusSelect.value; } // Переключатель вида @@ -266,8 +266,8 @@ if (this.state.filters.tag) { params.append('tag', this.state.filters.tag); } - if (this.state.filters.inStock) { - params.append('in_stock', 'true'); + if (this.state.filters.stockStatus !== 'all') { + params.append('stock_status', this.state.filters.stockStatus); } if (this.state.filters.warehouse) { params.append('warehouse', this.state.filters.warehouse); diff --git a/myproject/products/templates/products/components/product_search_picker.html b/myproject/products/templates/products/components/product_search_picker.html index 9efc507..e49dc53 100644 --- a/myproject/products/templates/products/components/product_search_picker.html +++ b/myproject/products/templates/products/components/product_search_picker.html @@ -90,16 +90,11 @@ ProductSearchPicker.init('#writeoff-products', { {% endif %} -
- - -
+ {% if show_view_toggle|default:True %} diff --git a/myproject/products/views/api_views.py b/myproject/products/views/api_views.py index 5d50fd9..0111215 100644 --- a/myproject/products/views/api_views.py +++ b/myproject/products/views/api_views.py @@ -21,10 +21,11 @@ def _get_product_photo_url(product_id): 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 товаров. Args: + stock_status: 'all' - все товары, 'in_stock' - только в наличии, 'out_of_stock' - только не в наличии 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) if tag_id: queryset = queryset.filter(tags__id=tag_id) - if in_stock_only: + if stock_status == 'in_stock': 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: # Фильтруем только товары, которые есть на указанном складе с доступным количеством # НЕ применяется при skip_stock_filter=True (приёмка товаров) @@ -58,7 +61,7 @@ def search_products_and_variants(request): - page: номер страницы для пагинации (по умолчанию 1) - category: ID категории для фильтрации (опционально) - tag: ID тега для фильтрации (опционально) - - in_stock: 'true' для фильтрации только товаров в наличии (опционально) + - stock_status: 'all' (все), 'in_stock' (в наличии), 'out_of_stock' (не в наличии) (опционально, по умолчанию 'all') - warehouse: ID склада для фильтрации только товаров с доступным остатком (опционально) Возвращает JSON в формате Select2 с группировкой: @@ -190,14 +193,14 @@ def search_products_and_variants(request): # Дополнительные фильтры category_id = request.GET.get('category', '').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() skip_stock_filter = request.GET.get('skip_stock_filter', '').lower() == 'true' 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: @@ -215,7 +218,7 @@ def search_products_and_variants(request): # Показываем последние добавленные активные товары 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] for product in products: @@ -359,7 +362,7 @@ def search_products_and_variants(request): ).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 для единиц продажи products_query = products_query.prefetch_related('sales_units__unit')