Унификация генерации номеров документов и оптимизация кода

- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX
- Убрано дублирование функции _extract_number_from_document_number
- Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией
- Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py)
- Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
This commit is contained in:
2025-12-21 00:51:08 +03:00
parent 78dc9e9801
commit 375ec5366a
14 changed files with 1873 additions and 147 deletions

View File

@@ -5,7 +5,8 @@ from decimal import Decimal
from .models import (
Warehouse, Incoming, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch,
TransferBatch, TransferItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock
TransferBatch, TransferItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock,
IncomingDocument, IncomingDocumentItem
)
from products.models import Product
@@ -557,3 +558,94 @@ class WriteOffDocumentItemForm(forms.ModelForm):
return cleaned_data
class IncomingDocumentForm(forms.ModelForm):
"""
Форма создания/редактирования документа поступления.
"""
class Meta:
model = IncomingDocument
fields = ['warehouse', 'date', 'receipt_type', 'supplier_name', 'notes']
widgets = {
'warehouse': forms.Select(attrs={'class': 'form-control'}),
'date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'receipt_type': forms.Select(attrs={'class': 'form-control'}),
'supplier_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Наименование поставщика'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Примечания к документу'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['warehouse'].queryset = Warehouse.objects.filter(is_active=True)
# Устанавливаем дату по умолчанию - сегодня
if not self.initial.get('date'):
from django.utils import timezone
self.initial['date'] = timezone.now().date()
# Если есть склад по умолчанию - предвыбираем его
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
# Устанавливаем тип поступления по умолчанию
if not self.initial.get('receipt_type'):
self.initial['receipt_type'] = 'supplier'
def clean(self):
cleaned_data = super().clean()
receipt_type = cleaned_data.get('receipt_type')
supplier_name = cleaned_data.get('supplier_name')
# Для типа 'supplier' supplier_name обязателен
if receipt_type == 'supplier' and not supplier_name:
raise ValidationError({
'supplier_name': 'Для типа "Поступление от поставщика" необходимо указать наименование поставщика'
})
# Для других типов supplier_name не нужен
if receipt_type != 'supplier' and supplier_name:
cleaned_data['supplier_name'] = None
return cleaned_data
class IncomingDocumentItemForm(forms.ModelForm):
"""
Форма добавления/редактирования позиции в документ поступления.
"""
class Meta:
model = IncomingDocumentItem
fields = ['product', 'quantity', 'cost_price', 'notes']
widgets = {
'product': forms.Select(attrs={'class': 'form-control'}),
'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001', 'min': '0.001'}),
'cost_price': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}),
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2, 'placeholder': 'Примечания'}),
}
def __init__(self, *args, document=None, **kwargs):
super().__init__(*args, **kwargs)
self.document = document
# Для поступлений можно выбрать любой активный товар (не нужно проверять наличие на складе)
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 is not None and cost_price < 0:
raise ValidationError('Закупочная цена не может быть отрицательной')
return cost_price