# -*- coding: utf-8 -*- from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.core.paginator import Paginator from django.http import JsonResponse from django.db import transaction from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required from django.core.exceptions import ValidationError from .models import Order, OrderItem from .forms import OrderForm, OrderItemFormSet from .filters import OrderFilter from .services import DraftOrderService from products.models import ProductKit, KitItem, Product import json def order_list(request): """ Список всех заказов с фильтрацией и поиском Использует django-filter для фильтрации данных """ # Базовый queryset с оптимизацией запросов orders = Order.objects.select_related( 'customer', 'delivery_address', 'pickup_shop' ).all() # Применяем фильтры через django-filter order_filter = OrderFilter(request.GET, queryset=orders) # Сортировка filtered_orders = order_filter.qs.order_by('-created_at') # Пагинация paginator = Paginator(filtered_orders, 25) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) context = { 'filter': order_filter, 'page_obj': page_obj, 'status_choices': Order.STATUS_CHOICES, } return render(request, 'orders/order_list.html', context) def order_detail(request, pk): """Детальная информация о заказе""" order = get_object_or_404( Order.objects.select_related('customer', 'delivery_address', 'pickup_shop', 'modified_by') .prefetch_related('items__product', 'items__product_kit', 'payments__created_by'), pk=pk ) context = { 'order': order, } return render(request, 'orders/order_detail.html', context) def order_create(request): """Создание нового заказа""" if request.method == 'POST': form = OrderForm(request.POST) formset = OrderItemFormSet(request.POST) if form.is_valid() and formset.is_valid(): order = form.save(commit=False) # Если нажата кнопка "Сохранить как черновик", создаем черновик if 'save_as_draft' in request.POST: order.status = 'draft' order.modified_by = request.user order.save() # Сохраняем позиции заказа formset.instance = order formset.save() # Пересчитываем итоговую сумму order.calculate_total() order.save() if order.is_draft(): messages.success(request, f'Черновик #{order.order_number} успешно создан!') else: messages.success(request, f'Заказ #{order.order_number} успешно создан!') return redirect('orders:order-detail', pk=order.pk) else: messages.error(request, 'Пожалуйста, исправьте ошибки в форме.') else: form = OrderForm() formset = OrderItemFormSet() context = { 'form': form, 'formset': formset, 'title': 'Создание заказа', 'button_text': 'Создать заказ', } return render(request, 'orders/order_form.html', context) def order_update(request, pk): """Редактирование заказа""" order = get_object_or_404(Order, pk=pk) if request.method == 'POST': form = OrderForm(request.POST, instance=order) formset = OrderItemFormSet(request.POST, instance=order) if form.is_valid() and formset.is_valid(): order = form.save(commit=False) # Если черновик финализируется if 'finalize_draft' in request.POST and order.is_draft(): try: order = DraftOrderService.finalize_draft(order.pk, request.user) messages.success(request, f'Черновик #{order.order_number} успешно завершен и переведен в статус "Новый"!') return redirect('orders:order-detail', pk=order.pk) except ValidationError as e: messages.error(request, f'Ошибка финализации: {str(e)}') form = OrderForm(instance=order) formset = OrderItemFormSet(instance=order) else: order.modified_by = request.user order.save() formset.save() # Пересчитываем итоговую сумму order.calculate_total() order.save() if order.is_draft(): messages.success(request, f'Черновик #{order.order_number} успешно обновлен!') else: messages.success(request, f'Заказ #{order.order_number} успешно обновлен!') return redirect('orders:order-detail', pk=order.pk) else: messages.error(request, 'Пожалуйста, исправьте ошибки в форме.') else: form = OrderForm(instance=order) formset = OrderItemFormSet(instance=order) context = { 'form': form, 'formset': formset, 'order': order, 'title': f'Редактирование {"черновика" if order.is_draft() else "заказа"} #{order.order_number}', 'button_text': 'Сохранить изменения', 'is_draft': order.is_draft(), } return render(request, 'orders/order_form.html', context) def order_delete(request, pk): """Удаление заказа с подтверждением""" order = get_object_or_404(Order, pk=pk) if request.method == 'POST': order_number = order.order_number order.delete() messages.success(request, f'Заказ #{order_number} успешно удален.') return redirect('orders:order-list') context = { 'order': order, } return render(request, 'orders/order_confirm_delete.html', context) # === AJAX ENDPOINTS === @require_http_methods(["POST"]) @login_required def autosave_draft_order(request, pk): """ AJAX endpoint для автосохранения черновика заказа. Принимает JSON с данными формы и обновляет черновик. Возвращает статус сохранения и время последнего сохранения. Пример запроса: { "customer": 1, "is_delivery": true, "delivery_address": 5, "delivery_date": "2024-01-15", "special_instructions": "Позвонить за час", "items": [ {"product_id": 10, "quantity": "2", "price": "500"}, {"product_kit_id": 5, "quantity": "1", "price": "1500"} ] } Ответ при успехе: { "success": true, "last_saved": "2024-01-10T15:30:45.123456", "order_id": 123, "order_number": "ORD-000123" } """ try: data = json.loads(request.body) # Проверяем существование заказа try: order = Order.objects.get(pk=pk) except Order.DoesNotExist: return JsonResponse({ 'success': False, 'error': 'Заказ не найден' }, status=404) # Проверяем, что это черновик if not order.is_draft(): return JsonResponse({ 'success': False, 'error': 'Можно автосохранять только черновики' }, status=400) # Используем DraftOrderService для обновления order = DraftOrderService.update_draft( order_id=pk, user=request.user, data=data ) # Обрабатываем позиции заказа, если они переданы if 'items' in data: # Удаляем существующие позиции order.items.all().delete() # Создаем новые позиции for item_data in data['items']: product_id = item_data.get('product_id') product_kit_id = item_data.get('product_kit_id') quantity = item_data.get('quantity') price = item_data.get('price') if product_id: DraftOrderService.add_item_to_draft( order_id=order.pk, product_id=product_id, quantity=quantity, price=price ) elif product_kit_id: DraftOrderService.add_item_to_draft( order_id=order.pk, product_kit_id=product_kit_id, quantity=quantity, price=price ) return JsonResponse({ 'success': True, 'last_saved': order.last_autosave_at.isoformat() if order.last_autosave_at else None, 'order_id': order.pk, 'order_number': order.order_number }) except ValidationError as e: return JsonResponse({ 'success': False, 'error': str(e) }, status=400) except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Некорректный JSON' }, status=400) except Exception as e: return JsonResponse({ 'success': False, 'error': f'Ошибка сервера: {str(e)}' }, status=500) @require_http_methods(["POST"]) @login_required def create_draft_from_form(request): """ AJAX endpoint для создания черновика заказа из формы создания. Используется для автоматического создания черновика при первом изменении формы. После создания возвращает ID черновика для перенаправления. Пример запроса: { "customer": 1, "is_delivery": true, "delivery_date": "2024-01-15" } Ответ при успехе: { "success": true, "order_id": 123, "order_number": "ORD-000123", "redirect_url": "/orders/123/edit/" } """ try: data = json.loads(request.body) # Получаем обязательное поле - клиента customer_id = data.get('customer') if not customer_id: return JsonResponse({ 'success': False, 'error': 'Необходимо выбрать клиента' }, status=400) from customers.models import Customer try: customer = Customer.objects.get(pk=customer_id) except Customer.DoesNotExist: return JsonResponse({ 'success': False, 'error': 'Клиент не найден' }, status=404) # Создаем черновик через DraftOrderService order = DraftOrderService.create_draft( user=request.user, customer=customer, data=data ) # Обрабатываем позиции заказа, если они переданы if 'items' in data: for item_data in data['items']: product_id = item_data.get('product_id') product_kit_id = item_data.get('product_kit_id') quantity = item_data.get('quantity') price = item_data.get('price') if product_id: DraftOrderService.add_item_to_draft( order_id=order.pk, product_id=product_id, quantity=quantity, price=price ) elif product_kit_id: DraftOrderService.add_item_to_draft( order_id=order.pk, product_kit_id=product_kit_id, quantity=quantity, price=price ) return JsonResponse({ 'success': True, 'order_id': order.pk, 'order_number': order.order_number, 'redirect_url': f'/orders/{order.pk}/edit/' }) except ValidationError as e: return JsonResponse({ 'success': False, 'error': str(e) }, status=400) except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Некорректный JSON' }, status=400) except Exception as e: return JsonResponse({ 'success': False, 'error': f'Ошибка сервера: {str(e)}' }, status=500) # === ВРЕМЕННЫЕ КОМПЛЕКТЫ === @require_http_methods(["POST"]) def create_temporary_kit(request): """ AJAX endpoint для создания временного комплекта. Используется при оформлении заказа для создания букета "на лету". Принимает JSON: { "name": "Букет для Анны", "description": "Красные розы и белые лилии", "order_id": 123, // опционально, если заказ уже создан "components": [ {"product_id": 1, "quantity": "5"}, {"product_id": 2, "quantity": "3"} ] } Возвращает JSON: { "success": true, "kit_id": 456, "kit_name": "Букет для Анны", "kit_sku": "KIT-000456", "kit_price": "1500.00", "message": "Временный комплект создан успешно" } """ import json from decimal import Decimal try: data = json.loads(request.body) name = data.get('name', '').strip() description = data.get('description', '').strip() order_id = data.get('order_id') components = data.get('components', []) # Валидация if not name: return JsonResponse({ 'success': False, 'error': 'Необходимо указать название комплекта' }, status=400) if not components or len(components) == 0: return JsonResponse({ 'success': False, 'error': 'Комплект должен содержать хотя бы один компонент' }, status=400) # Создаем временный комплект with transaction.atomic(): # Получаем заказ если указан order = None if order_id: try: order = Order.objects.get(pk=order_id) except Order.DoesNotExist: return JsonResponse({ 'success': False, 'error': f'Заказ #{order_id} не найден' }, status=404) # Создаем комплект kit = ProductKit.objects.create( name=name, description=description, is_temporary=True, is_active=True, order=order, price_adjustment_type='none' ) # Добавляем компоненты for component in components: product_id = component.get('product_id') quantity = component.get('quantity') if not product_id or not quantity: continue try: product = Product.objects.get(pk=product_id) KitItem.objects.create( kit=kit, product=product, quantity=Decimal(str(quantity)) ) except Product.DoesNotExist: # Пропускаем несуществующие товары continue except (ValueError, TypeError): # Пропускаем некорректные количества continue # Пересчитываем цену комплекта kit.recalculate_base_price() return JsonResponse({ 'success': True, 'kit_id': kit.id, 'kit_name': kit.name, 'kit_sku': kit.sku, 'kit_price': str(kit.actual_price), 'message': f'Временный комплект "{kit.name}" создан успешно' }) except json.JSONDecodeError: return JsonResponse({ 'success': False, 'error': 'Некорректный JSON' }, status=400) except Exception as e: return JsonResponse({ 'success': False, 'error': f'Ошибка при создании комплекта: {str(e)}' }, status=500)