""" Views для работы с документами списания (WriteOffDocument). """ 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 WriteOffDocument, WriteOffDocumentItem from inventory.forms import WriteOffDocumentForm, WriteOffDocumentItemForm from inventory.services.writeoff_document_service import WriteOffDocumentService class WriteOffDocumentListView(LoginRequiredMixin, ListView): """Список документов списания""" model = WriteOffDocument template_name = 'inventory/writeoff_document/list.html' context_object_name = 'documents' paginate_by = 20 def get_queryset(self): return WriteOffDocument.objects.select_related( 'warehouse', 'created_by', 'confirmed_by' ).prefetch_related('items').order_by('-date', '-created_at') class WriteOffDocumentCreateView(LoginRequiredMixin, CreateView): """Создание документа списания""" model = WriteOffDocument form_class = WriteOffDocumentForm template_name = 'inventory/writeoff_document/form.html' def get_initial(self): """Устанавливаем начальные значения для формы""" initial = super().get_initial() from django.utils import timezone initial['date'] = timezone.now().date() return initial def form_valid(self, form): document = WriteOffDocumentService.create_document( warehouse=form.cleaned_data['warehouse'], date=form.cleaned_data['date'], notes=form.cleaned_data.get('notes'), created_by=self.request.user ) messages.success(self.request, f'Документ {document.document_number} создан') return redirect('inventory:writeoff-document-detail', pk=document.pk) class WriteOffDocumentDetailView(LoginRequiredMixin, DetailView): """Детальный просмотр документа списания""" model = WriteOffDocument template_name = 'inventory/writeoff_document/detail.html' context_object_name = 'document' def get_queryset(self): return WriteOffDocument.objects.select_related( 'warehouse', 'created_by', 'confirmed_by' ).prefetch_related('items__product', 'items__reservation') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['item_form'] = WriteOffDocumentItemForm(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 WriteOffDocumentAddItemView(LoginRequiredMixin, View): """Добавление позиции в документ списания""" @transaction.atomic def post(self, request, pk): document = get_object_or_404(WriteOffDocument, pk=pk) form = WriteOffDocumentItemForm(request.POST, document=document) if form.is_valid(): try: item = WriteOffDocumentService.add_item( document=document, product=form.cleaned_data['product'], quantity=form.cleaned_data['quantity'], reason=form.cleaned_data['reason'], 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:writeoff-document-detail', pk=pk) class WriteOffDocumentUpdateItemView(LoginRequiredMixin, View): """Обновление позиции документа списания""" @transaction.atomic def post(self, request, pk, item_pk): document = get_object_or_404(WriteOffDocument, pk=pk) item = get_object_or_404(WriteOffDocumentItem, pk=item_pk, document=document) try: quantity = request.POST.get('quantity') reason = request.POST.get('reason') notes = request.POST.get('notes') WriteOffDocumentService.update_item( item, quantity=quantity, reason=reason, notes=notes ) 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:writeoff-document-detail', pk=pk) class WriteOffDocumentRemoveItemView(LoginRequiredMixin, View): """Удаление позиции из документа списания""" @transaction.atomic def post(self, request, pk, item_pk): document = get_object_or_404(WriteOffDocument, pk=pk) item = get_object_or_404(WriteOffDocumentItem, pk=item_pk, document=document) try: product_name = item.product.name WriteOffDocumentService.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:writeoff-document-detail', pk=pk) class WriteOffDocumentConfirmView(LoginRequiredMixin, View): """Проведение документа списания""" @transaction.atomic def post(self, request, pk): document = get_object_or_404(WriteOffDocument, pk=pk) try: result = WriteOffDocumentService.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:writeoff-document-detail', pk=pk) class WriteOffDocumentCancelView(LoginRequiredMixin, View): """Отмена документа списания""" @transaction.atomic def post(self, request, pk): document = get_object_or_404(WriteOffDocument, pk=pk) try: WriteOffDocumentService.cancel_document(document) messages.success(request, f'Документ {document.document_number} отменён') except ValidationError as e: messages.error(request, str(e)) return redirect('inventory:writeoff-document-list')