From e831c4fb6ed52cede99d843678efd4fac61b150f Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Fri, 2 Jan 2026 12:35:01 +0300 Subject: [PATCH] =?UTF-8?q?feat(products):=20=D1=80=D0=B5=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=81=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B5=D0=BC=D0=B0=20=D0=B5=D0=B4=D0=B8=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B4=D0=B0=D0=B6=D0=B8=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=B5=D0=BD=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена полноценная интеграция единиц измерения (UoM) для продажи товаров в разных единицах с автоматическим пересчётом цен и остатков. ## Основные изменения: ### Backend - Расширен API поиска товаров (api_views.py): добавлена сериализация sales_units - Создан новый endpoint get_product_sales_units_api для загрузки единиц с остатками - Добавлено поле sales_unit в OrderItemForm и SaleForm с валидацией - Созданы CRUD views для управления единицами продажи (uom_views.py) - Обновлена ProductForm: использует base_unit вместо устаревшего unit ### Frontend - Создан модуль sales-units.js с функциями для работы с единицами - Интегрирован в select2-product-search.js: автозагрузка единиц при выборе товара - Добавлены контейнеры для единиц в order_form.html и sale_form.html - Реализовано автоматическое обновление цены при смене единицы продажи - При выборе базовой единицы цена возвращается к базовой цене товара ### UI - Добавлены страницы управления единицами продажи в навбар - Созданы шаблоны: sales_unit_list.html, sales_unit_form.html, sales_unit_delete.html - Добавлены фильтры по товару, единице, активности и дефолтности ## Исправленные ошибки: - Порядок инициализации: обработчики устанавливаются ДО триггера события change - Цена корректно обновляется при выборе единицы продажи - При выборе "Базовая единица" возвращается базовая цена товара 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- myproject/inventory/forms.py | 19 +- .../templates/inventory/sale/sale_form.html | 95 +++++++ myproject/orders/forms.py | 18 +- .../orders/templates/orders/order_form.html | 11 +- myproject/products/admin.py | 27 +- myproject/products/forms.py | 110 +++++++- .../static/products/js/sales-units.js | 249 ++++++++++++++++++ .../products/js/select2-product-search.js | 98 ++++++- .../templates/products/product_detail.html | 9 +- .../templates/products/product_form.html | 12 +- .../products/uom/sales_unit_delete.html | 62 +++++ .../products/uom/sales_unit_form.html | 186 +++++++++++++ .../products/uom/sales_unit_list.html | 183 +++++++++++++ .../templates/products/uom/unit_list.html | 123 +++++++++ myproject/products/urls.py | 8 + myproject/products/views/__init__.py | 16 ++ myproject/products/views/api_views.py | 200 ++++++++++++-- myproject/products/views/uom_views.py | 197 ++++++++++++++ myproject/templates/navbar.html | 3 + 19 files changed, 1574 insertions(+), 52 deletions(-) create mode 100644 myproject/products/static/products/js/sales-units.js create mode 100644 myproject/products/templates/products/uom/sales_unit_delete.html create mode 100644 myproject/products/templates/products/uom/sales_unit_form.html create mode 100644 myproject/products/templates/products/uom/sales_unit_list.html create mode 100644 myproject/products/templates/products/uom/unit_list.html create mode 100644 myproject/products/views/uom_views.py diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py index 03b03db..19fe023 100644 --- a/myproject/inventory/forms.py +++ b/myproject/inventory/forms.py @@ -45,16 +45,33 @@ class WarehouseForm(forms.ModelForm): class SaleForm(forms.ModelForm): class Meta: model = Sale - fields = ['product', 'warehouse', 'quantity', 'sale_price', 'order', 'document_number'] + fields = ['product', 'warehouse', 'sales_unit', 'quantity', 'sale_price', 'order', 'document_number'] widgets = { 'product': forms.Select(attrs={'class': 'form-control'}), 'warehouse': forms.Select(attrs={'class': 'form-control'}), + 'sales_unit': forms.Select(attrs={'class': 'form-control'}), 'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), 'sale_price': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}), 'order': forms.Select(attrs={'class': 'form-control'}), 'document_number': forms.TextInput(attrs={'class': 'form-control'}), } + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Динамический queryset для sales_unit + if self.instance.pk and self.instance.product: + from products.models import ProductSalesUnit + self.fields['sales_unit'].queryset = ProductSalesUnit.objects.filter( + product=self.instance.product, + is_active=True + ).order_by('position', 'id') + else: + from products.models import ProductSalesUnit + self.fields['sales_unit'].queryset = ProductSalesUnit.objects.none() + + self.fields['sales_unit'].required = False + def clean_quantity(self): quantity = self.cleaned_data.get('quantity') if quantity and quantity <= 0: diff --git a/myproject/inventory/templates/inventory/sale/sale_form.html b/myproject/inventory/templates/inventory/sale/sale_form.html index d30f50b..50b38a8 100644 --- a/myproject/inventory/templates/inventory/sale/sale_form.html +++ b/myproject/inventory/templates/inventory/sale/sale_form.html @@ -52,6 +52,22 @@ + + +
@@ -295,6 +299,7 @@ +
@@ -308,6 +313,9 @@
+ + +
@@ -1809,7 +1817,8 @@ document.addEventListener('DOMContentLoaded', function() {
- + +