diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py index fd2a074..03b03db 100644 --- a/myproject/inventory/forms.py +++ b/myproject/inventory/forms.py @@ -68,41 +68,7 @@ class SaleForm(forms.ModelForm): return sale_price -class WriteOffForm(forms.ModelForm): - class Meta: - model = WriteOff - fields = ['batch', 'quantity', 'reason', 'document_number', 'notes'] - widgets = { - 'batch': forms.Select(attrs={'class': 'form-control'}), - 'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), - 'reason': forms.Select(attrs={'class': 'form-control'}), - 'document_number': forms.TextInput(attrs={'class': 'form-control'}), - 'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), - } - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # Фильтруем партии - показываем только активные - self.fields['batch'].queryset = StockBatch.objects.filter( - is_active=True - ).select_related('product', 'warehouse').order_by('-created_at') - - def clean(self): - cleaned_data = super().clean() - batch = cleaned_data.get('batch') - quantity = cleaned_data.get('quantity') - - if batch and quantity: - if quantity > batch.quantity: - raise ValidationError( - f'Невозможно списать {quantity} шт из партии, ' - f'где только {batch.quantity} шт. ' - f'Недостаток: {quantity - batch.quantity} шт.' - ) - if quantity <= 0: - raise ValidationError('Количество должно быть больше нуля') - - return cleaned_data +# WriteOffForm удалён - используйте WriteOffDocumentForm для работы с документами списания class InventoryForm(forms.ModelForm): diff --git a/myproject/inventory/templates/inventory/base_inventory_minimal.html b/myproject/inventory/templates/inventory/base_inventory_minimal.html index b3179c8..1aab649 100644 --- a/myproject/inventory/templates/inventory/base_inventory_minimal.html +++ b/myproject/inventory/templates/inventory/base_inventory_minimal.html @@ -26,7 +26,6 @@
  • Поступления
  • Продажи
  • Инвентаризация
  • -
  • Списания
  • Документы списания
  • Перемещения
  • diff --git a/myproject/inventory/templates/inventory/home.html b/myproject/inventory/templates/inventory/home.html index 9d99ca1..0f8e9e5 100644 --- a/myproject/inventory/templates/inventory/home.html +++ b/myproject/inventory/templates/inventory/home.html @@ -92,19 +92,6 @@ - -
    - -
    -
    - -
    Приходы
    - Поступление товара -
    -
    -
    -
    -
    @@ -118,19 +105,6 @@
    - -
    - -
    -
    - -
    Списания
    - Списание товара -
    -
    -
    -
    -
    diff --git a/myproject/inventory/templates/inventory/sale/sale_list.html b/myproject/inventory/templates/inventory/sale/sale_list.html index b0e0a28..b843e93 100644 --- a/myproject/inventory/templates/inventory/sale/sale_list.html +++ b/myproject/inventory/templates/inventory/sale/sale_list.html @@ -20,6 +20,7 @@ Склад Количество Цена продажи + Сумма Заказ Статус Дата @@ -33,6 +34,7 @@ {{ sale.warehouse.name }} {{ sale.quantity|smart_quantity }} шт {{ sale.sale_price }} руб. + {{ sale.quantity|multiply:sale.sale_price|format_decimal:2 }} руб. {% if sale.order %} {{ sale.order.order_number }} diff --git a/myproject/inventory/templates/inventory/writeoff/writeoff_confirm_delete.html b/myproject/inventory/templates/inventory/writeoff/writeoff_confirm_delete.html deleted file mode 100644 index c6917d1..0000000 --- a/myproject/inventory/templates/inventory/writeoff/writeoff_confirm_delete.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'inventory/base_inventory_minimal.html' %} -{% block inventory_title %}Отмена списания{% endblock %} -{% block breadcrumb_current %}Списания{% endblock %} -{% block inventory_content %} -
    -

    Подтверждение отмены

    -
    -
    -{% endblock %} diff --git a/myproject/inventory/templates/inventory/writeoff/writeoff_form.html b/myproject/inventory/templates/inventory/writeoff/writeoff_form.html deleted file mode 100644 index 0fc340d..0000000 --- a/myproject/inventory/templates/inventory/writeoff/writeoff_form.html +++ /dev/null @@ -1,335 +0,0 @@ -{% extends 'inventory/base_inventory_minimal.html' %} -{% load static inventory_filters %} -{% block inventory_title %}{% if form.instance.pk %}Редактирование списания{% else %}Новое списание{% endif %}{% endblock %} -{% block breadcrumb_current %}Списания{% endblock %} - -{% block inventory_content %} - - - -
    - -
    - {% include 'products/components/product_search_picker.html' with container_id='writeoff-product-picker' title='Найти товар для списания' filter_in_stock_only=True categories=categories tags=tags add_button_text='Выбрать товар' multi_select=False content_height='350px' %} -
    - - -
    -
    -
    -
    - - {% if form.instance.pk %}Редактирование{% else %}Создание{% endif %} списания -
    -
    -
    - - {% if form.non_field_errors %} - - {% endif %} - - - - -
    - {% csrf_token %} - - - - - -
    - - {{ form.batch }} - {% if form.batch.errors %} -
    {{ form.batch.errors.0 }}
    - {% endif %} - - Сначала выберите товар слева, затем здесь появятся его партии - - - -
    - - -
    - - {{ form.quantity|smart_quantity }} - {% if form.quantity.errors %} -
    {{ form.quantity.errors.0 }}
    - {% endif %} - - Введите количество товара для списания - - - -
    - - -
    - - {{ form.reason }} - {% if form.reason.errors %} -
    {{ form.reason.errors.0 }}
    - {% endif %} -
    - - -
    - - {{ form.document_number }} - {% if form.document_number.errors %} -
    {{ form.document_number.errors.0 }}
    - {% endif %} -
    - - -
    - - {{ form.notes }} - {% if form.notes.errors %} -
    {{ form.notes.errors.0 }}
    - {% endif %} -
    - - -
    - - - Отмена - -
    -
    -
    -
    -
    -
    - - - - - - -{% endblock %} diff --git a/myproject/inventory/templates/inventory/writeoff/writeoff_list.html b/myproject/inventory/templates/inventory/writeoff/writeoff_list.html deleted file mode 100644 index f15fbb1..0000000 --- a/myproject/inventory/templates/inventory/writeoff/writeoff_list.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends 'inventory/base_inventory_minimal.html' %} -{% load inventory_filters %} -{% block inventory_title %}История списаний{% endblock %} -{% block breadcrumb_current %}Списания{% endblock %} -{% block inventory_content %} -
    -
    -

    Списания товара

    - - Новое списание - -
    -
    - {% if writeoffs %} -
    - - - - - - - - - - - - {% for writeoff in writeoffs %} - - - - - - - - {% endfor %} - -
    ТоварКоличествоПричинаДатаДействия
    {{ writeoff.batch.product.name }}{{ writeoff.quantity|smart_quantity }} шт{{ writeoff.get_reason_display }}{{ writeoff.date|date:"d.m.Y H:i" }} - - - - - - -
    -
    - {% else %} -
    Списаний не найдено.
    - {% endif %} -
    -
    -{% endblock %} diff --git a/myproject/inventory/templatetags/inventory_filters.py b/myproject/inventory/templatetags/inventory_filters.py index e837b4e..d9974fe 100644 --- a/myproject/inventory/templatetags/inventory_filters.py +++ b/myproject/inventory/templatetags/inventory_filters.py @@ -87,12 +87,60 @@ def format_decimal(value, decimal_places=2): quantize_value = Decimal(10) ** -decimal_places rounded = num.quantize(quantize_value) - # Убираем лишние нули - normalized = rounded.normalize() + # Форматируем без научной нотации + result = format(rounded, 'f') - # Форматируем с запятой - result = str(normalized).replace('.', ',') + # Убираем лишние нули справа после десятичной точки + if '.' in result: + result = result.rstrip('0').rstrip('.') + + # Форматируем с запятой вместо точки + result = result.replace('.', ',') return result except (ValueError, TypeError, ArithmeticError): return str(value) + + +@register.filter(name='multiply') +def multiply(value, arg): + """ + Умножает значение на аргумент. + + Использование в шаблонах: + {{ quantity|multiply:price }} + + Args: + value: первое число + arg: второе число (множитель) + + Returns: + Decimal: результат умножения + """ + try: + if value is None or arg is None: + return 0 + + # Преобразуем в Decimal для точности + if isinstance(value, str): + val = Decimal(value) + elif isinstance(value, (int, float)): + val = Decimal(str(value)) + elif isinstance(value, Decimal): + val = value + else: + val = Decimal(str(value)) + + if isinstance(arg, str): + multiplier = Decimal(arg) + elif isinstance(arg, (int, float)): + multiplier = Decimal(str(arg)) + elif isinstance(arg, Decimal): + multiplier = arg + else: + multiplier = Decimal(str(arg)) + + return val * multiplier + + except (ValueError, TypeError, ArithmeticError): + return 0 diff --git a/myproject/inventory/urls.py b/myproject/inventory/urls.py index 0428e57..14e27bb 100644 --- a/myproject/inventory/urls.py +++ b/myproject/inventory/urls.py @@ -9,8 +9,6 @@ from .views import ( InventoryListView, InventoryCreateView, InventoryDetailView, InventoryLineCreateBulkView, InventoryLineAddView, InventoryLineUpdateView, InventoryLineDeleteView, InventoryCompleteView, InventoryDeleteView, - # WriteOff - WriteOffListView, WriteOffCreateView, WriteOffUpdateView, WriteOffDeleteView, # Transfer TransferListView, TransferBulkCreateView, TransferDetailView, TransferDeleteView, GetProductStockView, # Reservation @@ -83,10 +81,9 @@ urlpatterns = [ path('inventory-ops//delete/', InventoryDeleteView.as_view(), name='inventory-delete'), # ==================== WRITEOFF (одиночные записи) ==================== - path('writeoffs/', WriteOffListView.as_view(), name='writeoff-list'), - path('writeoffs/create/', WriteOffCreateView.as_view(), name='writeoff-create'), - path('writeoffs//edit/', WriteOffUpdateView.as_view(), name='writeoff-update'), - path('writeoffs//delete/', WriteOffDeleteView.as_view(), name='writeoff-delete'), + # УДАЛЕНО: пользователи работают только через WriteOffDocument + # Модель WriteOff остаётся - она используется для хранения фактических списаний + # Записи WriteOff создаются автоматически при проведении документов списания # ==================== WRITEOFF DOCUMENT (документы списания) ==================== path('writeoff-documents/', WriteOffDocumentListView.as_view(), name='writeoff-document-list'), diff --git a/myproject/inventory/views/__init__.py b/myproject/inventory/views/__init__.py index 992a626..6fa7abc 100644 --- a/myproject/inventory/views/__init__.py +++ b/myproject/inventory/views/__init__.py @@ -7,7 +7,7 @@ Inventory Views Package - incoming.py: Управление приходами товара - sale.py: Управление продажами - inventory_ops.py: Инвентаризация и её строки -- writeoff.py: Списания товара +- writeoff_document.py: Документы списания товара - transfer.py: Перемещения между складами - reservation.py: Резервирования товара (view-only) - stock.py: Справочник остатков (view-only) @@ -25,7 +25,6 @@ from .inventory_ops import ( InventoryLineCreateBulkView, InventoryLineAddView, InventoryLineUpdateView, InventoryLineDeleteView, InventoryCompleteView, InventoryDeleteView ) -from .writeoff import WriteOffListView, WriteOffCreateView, WriteOffUpdateView, WriteOffDeleteView from .writeoff_document import ( WriteOffDocumentListView, WriteOffDocumentCreateView, WriteOffDocumentDetailView, WriteOffDocumentAddItemView, WriteOffDocumentUpdateItemView, WriteOffDocumentRemoveItemView, @@ -61,8 +60,6 @@ __all__ = [ 'InventoryListView', 'InventoryCreateView', 'InventoryDetailView', 'InventoryLineCreateBulkView', 'InventoryLineAddView', 'InventoryLineUpdateView', 'InventoryLineDeleteView', 'InventoryCompleteView', 'InventoryDeleteView', - # WriteOff - 'WriteOffListView', 'WriteOffCreateView', 'WriteOffUpdateView', 'WriteOffDeleteView', # WriteOffDocument 'WriteOffDocumentListView', 'WriteOffDocumentCreateView', 'WriteOffDocumentDetailView', 'WriteOffDocumentAddItemView', 'WriteOffDocumentUpdateItemView', 'WriteOffDocumentRemoveItemView', diff --git a/myproject/inventory/views/writeoff.py b/myproject/inventory/views/writeoff.py deleted file mode 100644 index 0443889..0000000 --- a/myproject/inventory/views/writeoff.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -""" -WriteOff (Списание товара) views -GROUP 2: MEDIUM PRIORITY -""" -from django.views.generic import ListView, CreateView, UpdateView, DeleteView -from django.urls import reverse_lazy -from django.contrib.auth.mixins import LoginRequiredMixin -from django.contrib import messages -from ..models import WriteOff -from ..forms import WriteOffForm -from products.models import ProductCategory, ProductTag - - -class WriteOffListView(LoginRequiredMixin, ListView): - model = WriteOff - template_name = 'inventory/writeoff/writeoff_list.html' - context_object_name = 'writeoffs' - paginate_by = 20 - - def get_queryset(self): - return WriteOff.objects.select_related('batch', 'batch__product').order_by('-date') - - -class WriteOffCreateView(LoginRequiredMixin, CreateView): - model = WriteOff - form_class = WriteOffForm - template_name = 'inventory/writeoff/writeoff_form.html' - success_url = reverse_lazy('inventory:writeoff-list') - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name') - context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name') - return context - - def form_valid(self, form): - messages.success(self.request, f'Списание товара успешно создано.') - return super().form_valid(form) - - -class WriteOffUpdateView(LoginRequiredMixin, UpdateView): - model = WriteOff - form_class = WriteOffForm - template_name = 'inventory/writeoff/writeoff_form.html' - success_url = reverse_lazy('inventory:writeoff-list') - - def form_valid(self, form): - messages.success(self.request, f'Списание товара обновлено.') - return super().form_valid(form) - - -class WriteOffDeleteView(LoginRequiredMixin, DeleteView): - model = WriteOff - template_name = 'inventory/writeoff/writeoff_confirm_delete.html' - success_url = reverse_lazy('inventory:writeoff-list') - - def form_valid(self, form): - writeoff = self.get_object() - messages.success(self.request, f'Списание товара отменено.') - return super().form_valid(form)