Forms (orders/forms.py): - TemporaryKitForm: упрощенная форма для временного комплекта (название + описание) - TemporaryKitItemForm: форма для компонента временного комплекта - TemporaryKitItemFormSet: formset для управления компонентами Views (orders/views.py): - create_temporary_kit: AJAX endpoint для создания временного комплекта * Принимает JSON с названием, описанием и списком компонентов * Создает комплект с is_temporary=True * Связывает с заказом если указан order_id * Автоматически пересчитывает цену * Возвращает JSON с данными созданного комплекта URLs (orders/urls.py): - /orders/temporary-kits/create/ - endpoint для создания Теперь можно создавать временные комплекты через AJAX запрос. Следующий шаг - UI в форме заказа. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
267 lines
9.0 KiB
Python
267 lines
9.0 KiB
Python
# -*- 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 .models import Order, OrderItem
|
||
from .forms import OrderForm, OrderItemFormSet
|
||
from .filters import OrderFilter
|
||
from products.models import ProductKit, KitItem, Product
|
||
|
||
|
||
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)
|
||
order.save()
|
||
|
||
# Сохраняем позиции заказа
|
||
formset.instance = order
|
||
formset.save()
|
||
|
||
# Пересчитываем итоговую сумму
|
||
order.calculate_total()
|
||
order.save()
|
||
|
||
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()
|
||
formset.save()
|
||
|
||
# Пересчитываем итоговую сумму
|
||
order.calculate_total()
|
||
order.save()
|
||
|
||
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'Редактирование заказа #{order.order_number}',
|
||
'button_text': 'Сохранить изменения',
|
||
}
|
||
|
||
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)
|
||
|
||
|
||
# === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
||
|
||
@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)
|