Files
octopus/myproject/orders/views.py
Andrey Smakotin 3ba4ceab3d Добавлено автоматическое создание черновика при создании заказа
При любом изменении формы создания заказа автоматически создается черновик
и пользователь перенаправляется на страницу редактирования, где работает
стандартное автосохранение.

Изменения:
- Добавлен AJAX endpoint create_draft_from_form в views.py
- Создан JavaScript модуль draft-creator.js для автосоздания черновика
- Обновлен order_form.html для подключения соответствующих скриптов
- Добавлен URL маршрут /orders/create-draft/

Работает с задержкой 2 секунды после первого изменения формы.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 23:08:28 +03:00

505 lines
18 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.
# -*- 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)