Добавлен 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:
2025-11-08 15:09:12 +03:00
parent ea4bb5a43b
commit 17b2d706f7
3 changed files with 206 additions and 0 deletions

View File

@@ -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,
)

View File

@@ -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'),
]

View File

@@ -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)