From 961a5d52da450481155b8d232b531900c1b8e2e5 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Sat, 8 Nov 2025 21:20:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=BE=D0=B2=20(=D0=AD?= =?UTF-8?q?=D1=82=D0=B0=D0=BF=202/3):=20AJAX=20endpoints=20=D0=B8=20views?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены AJAX endpoints: - autosave_draft_order: endpoint для автосохранения черновиков Модифицированы views: - order_create: поддержка создания черновиков через кнопку 'save_as_draft' - order_update: поддержка обновления и финализации черновиков через DraftOrderService Добавлены URL: - /orders//autosave/ для автосохранения черновиков Следующий этап: JavaScript модуль автосохранения и UI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- myproject/orders/urls.py | 3 + myproject/orders/views.py | 158 +++++++++++++++++++++++++++++++++++--- 2 files changed, 152 insertions(+), 9 deletions(-) diff --git a/myproject/orders/urls.py b/myproject/orders/urls.py index 27db4c9..7114d2c 100644 --- a/myproject/orders/urls.py +++ b/myproject/orders/urls.py @@ -11,6 +11,9 @@ urlpatterns = [ path('/edit/', views.order_update, name='order-update'), path('/delete/', views.order_delete, name='order-delete'), + # AJAX endpoints + path('/autosave/', views.autosave_draft_order, name='order-autosave'), + # Временные комплекты path('temporary-kits/create/', views.create_temporary_kit, name='temporary-kit-create'), ] diff --git a/myproject/orders/views.py b/myproject/orders/views.py index 7e83977..c354ead 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -5,10 +5,14 @@ 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): @@ -64,6 +68,12 @@ def order_create(request): 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() # Сохраняем позиции заказа @@ -74,7 +84,10 @@ def order_create(request): order.calculate_total() order.save() - messages.success(request, f'Заказ #{order.order_number} успешно создан!') + 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, 'Пожалуйста, исправьте ошибки в форме.') @@ -101,15 +114,32 @@ def order_update(request, pk): formset = OrderItemFormSet(request.POST, instance=order) if form.is_valid() and formset.is_valid(): - order = form.save() - formset.save() + order = form.save(commit=False) - # Пересчитываем итоговую сумму - order.calculate_total() - order.save() + # Если черновик финализируется + 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() - messages.success(request, f'Заказ #{order.order_number} успешно обновлен!') - return redirect('orders:order-detail', pk=order.pk) + # Пересчитываем итоговую сумму + 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: @@ -120,8 +150,9 @@ def order_update(request, pk): 'form': form, 'formset': formset, 'order': order, - 'title': f'Редактирование заказа #{order.order_number}', + '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) @@ -144,6 +175,115 @@ def order_delete(request, pk): 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"])