From c9ff7786300be38d99b5a1fdb3918de44ec3969e Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Fri, 26 Dec 2025 17:33:00 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=83=D1=8E=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= =?UTF-8?q?=D1=83=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Удалена старая одноэтапная система incoming и оставлена только новая двухэтапная система IncomingDocument (черновик → проведение). Изменения: - URL структура изменена с /incoming-documents/ на /incoming/ - URL names: incoming-document-* → incoming-* - Удалены старые views, forms, templates для Incoming/IncomingBatch - Обновлена навигация и все ссылки в шаблонах - Модели IncomingBatch/Incoming сохранены как внутренняя архитектура Удалено ~1590 строк кода: - inventory/views/incoming.py (389 строк) - inventory/forms.py (206 строк старых форм) - inventory/admin.py (56 строк) - 4 шаблона incoming/*.html (895 строк) Обновлено: - inventory/urls.py - новая URL структура - inventory/views/incoming_document.py - обновлены redirects - Все шаблоны с ссылками на incoming 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- myproject/inventory/admin.py | 56 -- myproject/inventory/forms.py | 206 ------ .../inventory/base_inventory_minimal.html | 4 +- .../inventory/templates/inventory/home.html | 2 +- .../incoming/incoming_bulk_form.html | 585 ------------------ .../incoming/incoming_confirm_delete.html | 50 -- .../inventory/incoming/incoming_form.html | 134 ---- .../inventory/incoming/incoming_list.html | 126 ---- .../incoming_document_detail.html | 14 +- .../incoming_document_form.html | 4 +- .../incoming_document_list.html | 6 +- .../inventory/inventory/inventory_detail.html | 2 +- myproject/inventory/urls.py | 27 +- myproject/inventory/views/__init__.py | 3 - myproject/inventory/views/incoming.py | 389 ------------ .../inventory/views/incoming_document.py | 12 +- 16 files changed, 30 insertions(+), 1590 deletions(-) delete mode 100644 myproject/inventory/templates/inventory/incoming/incoming_bulk_form.html delete mode 100644 myproject/inventory/templates/inventory/incoming/incoming_confirm_delete.html delete mode 100644 myproject/inventory/templates/inventory/incoming/incoming_form.html delete mode 100644 myproject/inventory/templates/inventory/incoming/incoming_list.html delete mode 100644 myproject/inventory/views/incoming.py diff --git a/myproject/inventory/admin.py b/myproject/inventory/admin.py index 0d413fc..a340bee 100644 --- a/myproject/inventory/admin.py +++ b/myproject/inventory/admin.py @@ -93,62 +93,6 @@ class StockBatchAdmin(admin.ModelAdmin): quantity_display.short_description = 'Количество' -# ===== INCOMING BATCH ===== -@admin.register(IncomingBatch) -class IncomingBatchAdmin(admin.ModelAdmin): - list_display = ('document_number', 'warehouse', 'receipt_type_display', 'supplier_name', 'items_count', 'created_at') - list_filter = ('warehouse', 'receipt_type', 'created_at') - search_fields = ('document_number', 'supplier_name') - date_hierarchy = 'created_at' - fieldsets = ( - ('Партия поступления', { - 'fields': ('document_number', 'warehouse', 'receipt_type', 'supplier_name', 'notes') - }), - ('Даты', { - 'fields': ('created_at', 'updated_at'), - 'classes': ('collapse',) - }), - ) - readonly_fields = ('created_at', 'updated_at') - - def items_count(self, obj): - return obj.items.count() - items_count.short_description = 'Товаров' - - def receipt_type_display(self, obj): - colors = { - 'supplier': '#0d6efd', # primary (синий) - 'inventory': '#0dcaf0', # info (голубой) - 'adjustment': '#198754', # success (зеленый) - } - color = colors.get(obj.receipt_type, '#6c757d') - return format_html( - '{}', - color, - obj.get_receipt_type_display() - ) - receipt_type_display.short_description = 'Тип поступления' - - -# ===== INCOMING ===== -@admin.register(Incoming) -class IncomingAdmin(admin.ModelAdmin): - list_display = ('product', 'batch', 'quantity', 'cost_price', 'created_at') - list_filter = ('batch__warehouse', 'created_at', 'product') - search_fields = ('product__name', 'batch__document_number') - date_hierarchy = 'created_at' - fieldsets = ( - ('Товар в партии', { - 'fields': ('batch', 'product', 'quantity', 'cost_price', 'stock_batch') - }), - ('Дата', { - 'fields': ('created_at',), - 'classes': ('collapse',) - }), - ) - readonly_fields = ('created_at', 'stock_batch') - - # ===== SALE BATCH ALLOCATION (INLINE) ===== class SaleBatchAllocationInline(admin.TabularInline): model = SaleBatchAllocation diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py index 84cab83..5d169e4 100644 --- a/myproject/inventory/forms.py +++ b/myproject/inventory/forms.py @@ -147,212 +147,6 @@ class InventoryLineForm(forms.ModelForm): return quantity_fact -# ============================================================================ -# INCOMING FORMS - Ввод товаров (один или много) от одного поставщика -# ============================================================================ - -class IncomingHeaderForm(forms.Form): - """ - Форма для общей информации при приходе товаров. - Используется для ввода информации об источнике поступления (склад, номер документа, поставщик). - """ - warehouse = forms.ModelChoiceField( - queryset=Warehouse.objects.filter(is_active=True), - widget=forms.Select(attrs={'class': 'form-control'}), - label="Склад", - required=True - ) - - document_number = forms.CharField( - max_length=100, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'PO-2024-001 (опционально)'}), - label="Номер документа / ПО", - required=False - ) - - supplier_name = forms.CharField( - max_length=200, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'ООО Поставщик'}), - label="Наименование поставщика", - required=False - ) - - notes = forms.CharField( - widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Дополнительная информация'}), - label="Примечания", - required=False - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Если есть склад по умолчанию и значение не установлено явно - предвыбираем его - if not self.initial.get('warehouse'): - default_warehouse = Warehouse.objects.filter( - is_active=True, - is_default=True - ).first() - if default_warehouse: - self.initial['warehouse'] = default_warehouse.id - - def clean_document_number(self): - document_number = self.cleaned_data.get('document_number', '') - if document_number: - document_number = document_number.strip() - # Запретить номера, начинающиеся с "IN-" (зарезервировано для системы) - if document_number.upper().startswith('IN-'): - raise ValidationError( - 'Номера, начинающиеся с "IN-", зарезервированы для системы автогенерации. ' - 'Оставьте поле пустым для автогенерации или используйте другой формат.' - ) - return document_number - - -class IncomingLineForm(forms.Form): - """ - Форма для одной строки товара при массовом приходе. - Используется в formset'е для динамического ввода нескольких товаров. - """ - product = forms.ModelChoiceField( - queryset=None, # Будет установлено в __init__ - widget=forms.Select(attrs={'class': 'form-control'}), - label="Товар", - required=True - ) - - quantity = forms.DecimalField( - max_digits=10, - decimal_places=3, - widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), - label="Количество", - required=True - ) - - cost_price = forms.DecimalField( - max_digits=10, - decimal_places=2, - widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}), - label="Цена закупки за ед.", - required=True - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Устанавливаем queryset товаров для поля product - self.fields['product'].queryset = Product.objects.filter( - is_active=True - ).order_by('name') - - def clean_quantity(self): - quantity = self.cleaned_data.get('quantity') - if quantity and quantity <= 0: - raise ValidationError('Количество должно быть больше нуля') - return quantity - - def clean_cost_price(self): - cost_price = self.cleaned_data.get('cost_price') - if cost_price and cost_price < 0: - raise ValidationError('Цена не может быть отрицательной') - return cost_price - - -class IncomingForm(forms.Form): - """ - Комбинированная форма для ввода товаров (один или много). - Содержит header информацию (склад, документ, поставщик) + динамический набор товаров. - """ - warehouse = forms.ModelChoiceField( - queryset=Warehouse.objects.filter(is_active=True), - widget=forms.Select(attrs={'class': 'form-control'}), - label="Склад", - required=True - ) - - document_number = forms.CharField( - max_length=100, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'PO-2024-001 (опционально)'}), - label="Номер документа / ПО", - required=False - ) - - receipt_type = forms.CharField( - max_length=20, - widget=forms.HiddenInput(), - initial='supplier', - required=False - ) - - supplier_name = forms.CharField( - max_length=200, - widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'ООО Поставщик'}), - label="Наименование поставщика (опционально)", - required=False - ) - - notes = forms.CharField( - widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Дополнительная информация'}), - label="Примечания", - required=False - ) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Если есть склад по умолчанию и значение не установлено явно - предвыбираем его - if not self.initial.get('warehouse'): - default_warehouse = Warehouse.objects.filter( - is_active=True, - is_default=True - ).first() - if default_warehouse: - self.initial['warehouse'] = default_warehouse.id - - def clean_document_number(self): - document_number = self.cleaned_data.get('document_number', '') - if document_number: - document_number = document_number.strip() - # Запретить номера, начинающиеся с "IN-" (зарезервировано для системы) - if document_number.upper().startswith('IN-'): - raise ValidationError( - 'Номера, начинающиеся с "IN-", зарезервированы для системы автогенерации. ' - 'Оставьте поле пустым для автогенерации или используйте другой формат.' - ) - return document_number - - -class IncomingModelForm(forms.ModelForm): - """ - ModelForm для редактирования отдельного товара в поступлении (Incoming). - Используется в IncomingUpdateView для редактирования существующих товаров. - """ - class Meta: - model = Incoming - fields = ['product', 'quantity', 'cost_price', 'notes'] - widgets = { - 'product': forms.Select(attrs={'class': 'form-control'}), - 'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), - 'cost_price': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}), - 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Фильтруем только активные товары - self.fields['product'].queryset = Product.objects.filter( - status='active' - ).order_by('name') - - def clean_quantity(self): - quantity = self.cleaned_data.get('quantity') - if quantity and quantity <= 0: - raise ValidationError('Количество должно быть больше нуля') - return quantity - - def clean_cost_price(self): - cost_price = self.cleaned_data.get('cost_price') - if cost_price and cost_price < 0: - raise ValidationError('Цена не может быть отрицательной') - return cost_price - - # ============================================================================ # TRANSFER FORMS - Перемещение товаров между складами # ============================================================================ diff --git a/myproject/inventory/templates/inventory/base_inventory_minimal.html b/myproject/inventory/templates/inventory/base_inventory_minimal.html index 5db0ec8..86ffc2d 100644 --- a/myproject/inventory/templates/inventory/base_inventory_minimal.html +++ b/myproject/inventory/templates/inventory/base_inventory_minimal.html @@ -23,9 +23,7 @@