Добавлен backend для создания временных комплектов в заказах
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>
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
@@ -10,4 +10,7 @@ urlpatterns = [
|
||||
path('<int:pk>/', views.order_detail, name='order-detail'),
|
||||
path('<int:pk>/edit/', views.order_update, name='order-update'),
|
||||
path('<int:pk>/delete/', views.order_delete, name='order-delete'),
|
||||
|
||||
# Временные комплекты
|
||||
path('temporary-kits/create/', views.create_temporary_kit, name='temporary-kit-create'),
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user