Files
octopus/myproject/inventory/views/incoming_document.py
Andrey Smakotin 375ec5366a Унификация генерации номеров документов и оптимизация кода
- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX
- Убрано дублирование функции _extract_number_from_document_number
- Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией
- Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py)
- Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
2025-12-21 00:51:08 +03:00

218 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Views для работы с документами поступления (IncomingDocument).
"""
from django.views.generic import ListView, CreateView, DetailView, View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.shortcuts import get_object_or_404, redirect
from django.contrib import messages
from django.http import JsonResponse
from django.db import transaction
from django.core.exceptions import ValidationError
from inventory.models import IncomingDocument, IncomingDocumentItem
from inventory.forms import IncomingDocumentForm, IncomingDocumentItemForm
from inventory.services.incoming_document_service import IncomingDocumentService
class IncomingDocumentListView(LoginRequiredMixin, ListView):
"""Список документов поступления"""
model = IncomingDocument
template_name = 'inventory/incoming_document/incoming_document_list.html'
context_object_name = 'documents'
paginate_by = 20
def get_queryset(self):
return IncomingDocument.objects.select_related(
'warehouse', 'created_by', 'confirmed_by'
).prefetch_related('items').order_by('-date', '-created_at')
class IncomingDocumentCreateView(LoginRequiredMixin, CreateView):
"""Создание документа поступления"""
model = IncomingDocument
form_class = IncomingDocumentForm
template_name = 'inventory/incoming_document/incoming_document_form.html'
def form_valid(self, form):
document = IncomingDocumentService.create_document(
warehouse=form.cleaned_data['warehouse'],
date=form.cleaned_data['date'],
receipt_type=form.cleaned_data['receipt_type'],
supplier_name=form.cleaned_data.get('supplier_name'),
notes=form.cleaned_data.get('notes'),
created_by=self.request.user
)
messages.success(self.request, f'Документ {document.document_number} создан')
return redirect('inventory:incoming-document-detail', pk=document.pk)
class IncomingDocumentDetailView(LoginRequiredMixin, DetailView):
"""Детальный просмотр документа поступления"""
model = IncomingDocument
template_name = 'inventory/incoming_document/incoming_document_detail.html'
context_object_name = 'document'
def get_queryset(self):
return IncomingDocument.objects.select_related(
'warehouse', 'created_by', 'confirmed_by'
).prefetch_related('items__product')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['item_form'] = IncomingDocumentItemForm(document=self.object)
# Добавляем категории и теги для компонента поиска товаров
from products.models import ProductCategory, ProductTag
context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
return context
class IncomingDocumentAddItemView(LoginRequiredMixin, View):
"""Добавление позиции в документ поступления"""
@transaction.atomic
def post(self, request, pk):
document = get_object_or_404(IncomingDocument, pk=pk)
form = IncomingDocumentItemForm(request.POST, document=document)
if form.is_valid():
try:
item = IncomingDocumentService.add_item(
document=document,
product=form.cleaned_data['product'],
quantity=form.cleaned_data['quantity'],
cost_price=form.cleaned_data['cost_price'],
notes=form.cleaned_data.get('notes')
)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({
'success': True,
'item_id': item.id,
'message': f'Добавлено: {item.product.name}'
})
messages.success(request, f'Добавлено: {item.product.name}')
except ValidationError as e:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({'success': False, 'error': str(e)}, status=400)
messages.error(request, str(e))
else:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
errors = '; '.join([f"{k}: {v[0]}" for k, v in form.errors.items()])
return JsonResponse({'success': False, 'error': errors}, status=400)
for field, errors in form.errors.items():
for error in errors:
messages.error(request, f'{field}: {error}')
return redirect('inventory:incoming-document-detail', pk=pk)
class IncomingDocumentUpdateItemView(LoginRequiredMixin, View):
"""Обновление позиции документа поступления"""
@transaction.atomic
def post(self, request, pk, item_pk):
document = get_object_or_404(IncomingDocument, pk=pk)
item = get_object_or_404(IncomingDocumentItem, pk=item_pk, document=document)
try:
quantity = request.POST.get('quantity')
cost_price = request.POST.get('cost_price')
notes = request.POST.get('notes')
IncomingDocumentService.update_item(
item,
quantity=quantity if quantity else None,
cost_price=cost_price if cost_price else None,
notes=notes if notes else None
)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({
'success': True,
'message': f'Обновлено: {item.product.name}'
})
messages.success(request, f'Обновлено: {item.product.name}')
except ValidationError as e:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({'success': False, 'error': str(e)}, status=400)
messages.error(request, str(e))
return redirect('inventory:incoming-document-detail', pk=pk)
class IncomingDocumentRemoveItemView(LoginRequiredMixin, View):
"""Удаление позиции из документа поступления"""
@transaction.atomic
def post(self, request, pk, item_pk):
document = get_object_or_404(IncomingDocument, pk=pk)
item = get_object_or_404(IncomingDocumentItem, pk=item_pk, document=document)
try:
product_name = item.product.name
IncomingDocumentService.remove_item(item)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({
'success': True,
'message': f'Удалено: {product_name}'
})
messages.success(request, f'Удалено: {product_name}')
except ValidationError as e:
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return JsonResponse({'success': False, 'error': str(e)}, status=400)
messages.error(request, str(e))
return redirect('inventory:incoming-document-detail', pk=pk)
class IncomingDocumentConfirmView(LoginRequiredMixin, View):
"""Проведение документа поступления"""
@transaction.atomic
def post(self, request, pk):
document = get_object_or_404(IncomingDocument, pk=pk)
try:
result = IncomingDocumentService.confirm_document(
document,
confirmed_by=request.user
)
messages.success(
request,
f'Документ {document.document_number} проведён. '
f'Оприходовано {result["total_quantity"]} шт на сумму {result["total_cost"]:.2f}'
)
except ValidationError as e:
messages.error(request, str(e))
return redirect('inventory:incoming-document-detail', pk=pk)
class IncomingDocumentCancelView(LoginRequiredMixin, View):
"""Отмена документа поступления"""
@transaction.atomic
def post(self, request, pk):
document = get_object_or_404(IncomingDocument, pk=pk)
try:
IncomingDocumentService.cancel_document(document)
messages.success(request, f'Документ {document.document_number} отменён')
except ValidationError as e:
messages.error(request, str(e))
return redirect('inventory:incoming-document-list')