diff --git a/myproject/orders/forms.py b/myproject/orders/forms.py index ba8650c..a08464b 100644 --- a/myproject/orders/forms.py +++ b/myproject/orders/forms.py @@ -154,3 +154,80 @@ OrderItemFormSet = inlineformset_factory( min_num=1, # Минимум 1 товар в заказе validate_min=True, ) + + +# === ВРЕМЕННЫЕ КОМПЛЕКТЫ === + +class TemporaryKitForm(forms.ModelForm): + """ + Упрощенная форма для создания временного комплекта. + Используется при оформлении заказа для создания букета "на лету". + """ + + class Meta: + model = ProductKit + fields = ['name', 'description'] + widgets = { + 'description': forms.Textarea(attrs={'rows': 2, 'placeholder': 'Краткое описание (опционально)'}), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Bootstrap классы + for field in self.fields.values(): + if isinstance(field.widget, forms.Textarea): + field.widget.attrs.update({'class': 'form-control'}) + else: + field.widget.attrs.update({'class': 'form-control'}) + + # Название обязательно + self.fields['name'].required = True + self.fields['name'].widget.attrs.update({ + 'placeholder': 'Название временного букета (например: "Букет для Анны")' + }) + + # Описание опционально + self.fields['description'].required = False + + +class TemporaryKitItemForm(forms.Form): + """ + Форма для компонента временного комплекта. + Используется в формсете для добавления товаров в букет. + """ + product = forms.IntegerField(required=False, widget=forms.HiddenInput()) + quantity = forms.DecimalField( + required=False, + min_value=0.001, + max_digits=10, + decimal_places=3, + widget=forms.NumberInput(attrs={'class': 'form-control', 'min': '0.001', 'step': '1'}) + ) + + def clean(self): + cleaned_data = super().clean() + product_id = cleaned_data.get('product') + quantity = cleaned_data.get('quantity') + + # Пустая форма - это нормально (будет удалена) + if not product_id: + return cleaned_data + + # Если выбран товар, количество обязательно + if product_id and (not quantity or quantity <= 0): + raise forms.ValidationError('Необходимо указать количество больше 0') + + return cleaned_data + + +# Formset для компонентов временного комплекта +from django.forms import formset_factory + +TemporaryKitItemFormSet = formset_factory( + TemporaryKitItemForm, + extra=1, # Одна пустая форма для добавления + can_delete=True, + min_num=1, # Минимум 1 компонент в комплекте + validate_min=True, +) diff --git a/myproject/orders/urls.py b/myproject/orders/urls.py index 31b3442..27db4c9 100644 --- a/myproject/orders/urls.py +++ b/myproject/orders/urls.py @@ -10,4 +10,7 @@ urlpatterns = [ path('/', views.order_detail, name='order-detail'), path('/edit/', views.order_update, name='order-update'), path('/delete/', views.order_delete, name='order-delete'), + + # Временные комплекты + 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 bb95f5f..7e83977 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -2,9 +2,13 @@ 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): @@ -138,3 +142,125 @@ def order_delete(request, pk): } 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)