Добавлены формы для работы с документами списания
- WriteOffDocumentForm - создание/редактирование документа списания - WriteOffDocumentItemForm - добавление/редактирование позиций документа - Автоматическая установка текущей даты и склада по умолчанию - Фильтрация товаров по наличию на выбранном складе - Валидация количества с проверкой доступных остатков - Учет текущего резерва при редактировании позиций
This commit is contained in:
@@ -5,7 +5,7 @@ from decimal import Decimal
|
|||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Warehouse, Incoming, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch,
|
Warehouse, Incoming, Sale, WriteOff, Transfer, Reservation, Inventory, InventoryLine, StockBatch,
|
||||||
TransferBatch, TransferItem, Showcase
|
TransferBatch, TransferItem, Showcase, WriteOffDocument, WriteOffDocumentItem, Stock
|
||||||
)
|
)
|
||||||
from products.models import Product
|
from products.models import Product
|
||||||
|
|
||||||
@@ -443,3 +443,110 @@ class TransferBulkForm(forms.Form):
|
|||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# WRITEOFF DOCUMENT FORMS - Документы списания
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
class WriteOffDocumentForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Форма создания/редактирования документа списания.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = WriteOffDocument
|
||||||
|
fields = ['warehouse', 'date', 'notes']
|
||||||
|
widgets = {
|
||||||
|
'warehouse': forms.Select(attrs={'class': 'form-control'}),
|
||||||
|
'date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'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
|
||||||
|
|
||||||
|
|
||||||
|
class WriteOffDocumentItemForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Форма добавления/редактирования позиции в документ списания.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = WriteOffDocumentItem
|
||||||
|
fields = ['product', 'quantity', 'reason', 'notes']
|
||||||
|
widgets = {
|
||||||
|
'product': forms.Select(attrs={'class': 'form-control'}),
|
||||||
|
'quantity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001', 'min': '0.001'}),
|
||||||
|
'reason': forms.Select(attrs={'class': 'form-control'}),
|
||||||
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 2, 'placeholder': 'Примечания'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, document=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.document = document
|
||||||
|
|
||||||
|
if document:
|
||||||
|
# Фильтруем товары - только те, что есть на складе
|
||||||
|
products_with_stock = Stock.objects.filter(
|
||||||
|
warehouse=document.warehouse,
|
||||||
|
quantity_available__gt=0
|
||||||
|
).values_list('product_id', flat=True)
|
||||||
|
|
||||||
|
self.fields['product'].queryset = Product.objects.filter(
|
||||||
|
id__in=products_with_stock,
|
||||||
|
status='active'
|
||||||
|
).order_by('name')
|
||||||
|
else:
|
||||||
|
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(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
product = cleaned_data.get('product')
|
||||||
|
quantity = cleaned_data.get('quantity')
|
||||||
|
|
||||||
|
if product and quantity and self.document:
|
||||||
|
stock = Stock.objects.filter(
|
||||||
|
product=product,
|
||||||
|
warehouse=self.document.warehouse
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not stock:
|
||||||
|
raise ValidationError({
|
||||||
|
'product': f'Товар "{product.name}" отсутствует на складе'
|
||||||
|
})
|
||||||
|
|
||||||
|
available = stock.quantity_available - stock.quantity_reserved
|
||||||
|
|
||||||
|
# Учитываем текущий резерв при редактировании
|
||||||
|
if self.instance.pk and self.instance.reservation:
|
||||||
|
available += self.instance.reservation.quantity
|
||||||
|
|
||||||
|
if quantity > available:
|
||||||
|
raise ValidationError({
|
||||||
|
'quantity': f'Недостаточно свободного товара. '
|
||||||
|
f'Доступно: {available}, запрашивается: {quantity}'
|
||||||
|
})
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user