""" 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-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-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-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-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-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-list')