refactor: мигрировать на новую систему документов поступления
Удалена старая одноэтапная система 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 - Перемещение товаров между складами
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user