Изменения: - Удалена функция create_temporary_kit из myproject/orders/views.py - Перенесена в новый сервис myproject/products/services/kit_service.py - Добавлен API endpoint products:api-temporary-kit-create для создания временных комплектов - Обновлены URL-ы соответственно Преимущества: - Логика временных комплектов теперь находится в соответствующем приложении (products) - Упрощена архитектура orders приложения - Сервис может быть переиспользован в других контекстах - Лучшее разделение ответственности между приложениями 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
160 lines
5.8 KiB
Python
160 lines
5.8 KiB
Python
"""
|
||
Сервис для работы с комплектами товаров.
|
||
Содержит бизнес-логику создания, обновления и управления комплектами.
|
||
"""
|
||
from decimal import Decimal
|
||
from django.db import transaction
|
||
from typing import List, Dict, Optional
|
||
|
||
from ..models import ProductKit, Product, KitItem
|
||
|
||
|
||
def create_temporary_kit(
|
||
name: str,
|
||
components: List[Dict],
|
||
description: str = '',
|
||
order=None
|
||
) -> ProductKit:
|
||
"""
|
||
Создает временный комплект с компонентами.
|
||
|
||
Временные комплекты используются для создания букетов "на лету" при оформлении заказа.
|
||
Они автоматически помечаются как временные (is_temporary=True) и связываются с заказом.
|
||
|
||
Args:
|
||
name: Название комплекта
|
||
components: Список компонентов в формате [{"product_id": 1, "quantity": "5"}, ...]
|
||
description: Описание комплекта (опционально)
|
||
order: Заказ, к которому привязан временный комплект (опционально)
|
||
|
||
Returns:
|
||
ProductKit: Созданный временный комплект
|
||
|
||
Raises:
|
||
ValueError: Если данные невалидны
|
||
Product.DoesNotExist: Если товар не найден
|
||
|
||
Example:
|
||
>>> kit = create_temporary_kit(
|
||
... name="Букет для Анны",
|
||
... description="Красные розы и белые лилии",
|
||
... components=[
|
||
... {"product_id": 1, "quantity": "5"},
|
||
... {"product_id": 2, "quantity": "3"}
|
||
... ]
|
||
... )
|
||
"""
|
||
# Валидация
|
||
if not name or not name.strip():
|
||
raise ValueError('Необходимо указать название комплекта')
|
||
|
||
if not components or len(components) == 0:
|
||
raise ValueError('Комплект должен содержать хотя бы один компонент')
|
||
|
||
with transaction.atomic():
|
||
# Создаем комплект
|
||
kit = ProductKit.objects.create(
|
||
name=name.strip(),
|
||
description=description.strip() if description else '',
|
||
is_temporary=True,
|
||
is_active=True,
|
||
order=order,
|
||
price_adjustment_type='none'
|
||
)
|
||
|
||
# Добавляем компоненты
|
||
added_count = 0
|
||
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, is_active=True)
|
||
KitItem.objects.create(
|
||
kit=kit,
|
||
product=product,
|
||
quantity=Decimal(str(quantity))
|
||
)
|
||
added_count += 1
|
||
except Product.DoesNotExist:
|
||
# Пропускаем несуществующие товары
|
||
continue
|
||
except (ValueError, TypeError) as e:
|
||
# Пропускаем некорректные количества
|
||
continue
|
||
|
||
if added_count == 0:
|
||
raise ValueError('Не удалось добавить ни одного компонента в комплект')
|
||
|
||
# Пересчитываем цену комплекта на основе компонентов
|
||
kit.recalculate_base_price()
|
||
|
||
return kit
|
||
|
||
|
||
def make_kit_permanent(kit: ProductKit) -> bool:
|
||
"""
|
||
Преобразует временный комплект в постоянный.
|
||
|
||
Args:
|
||
kit: Комплект для преобразования
|
||
|
||
Returns:
|
||
bool: True если комплект был преобразован, False если уже постоянный
|
||
"""
|
||
if not kit.is_temporary:
|
||
return False
|
||
|
||
kit.is_temporary = False
|
||
kit.order = None # Отвязываем от заказа
|
||
kit.save()
|
||
return True
|
||
|
||
|
||
def duplicate_kit(kit: ProductKit, new_name: Optional[str] = None) -> ProductKit:
|
||
"""
|
||
Создает копию комплекта со всеми компонентами.
|
||
|
||
Args:
|
||
kit: Комплект для дублирования
|
||
new_name: Новое название (если None, используется "Копия {original_name}")
|
||
|
||
Returns:
|
||
ProductKit: Новый комплект-копия
|
||
"""
|
||
with transaction.atomic():
|
||
# Копируем комплект
|
||
new_kit = ProductKit.objects.create(
|
||
name=new_name or f"Копия {kit.name}",
|
||
description=kit.description,
|
||
short_description=kit.short_description,
|
||
price_adjustment_type=kit.price_adjustment_type,
|
||
price_adjustment_value=kit.price_adjustment_value,
|
||
sale_price=kit.sale_price,
|
||
is_temporary=False, # Копия всегда постоянная
|
||
is_active=kit.is_active
|
||
)
|
||
|
||
# Копируем категории
|
||
new_kit.categories.set(kit.categories.all())
|
||
|
||
# Копируем теги
|
||
new_kit.tags.set(kit.tags.all())
|
||
|
||
# Копируем компоненты
|
||
for item in kit.kit_items.all():
|
||
KitItem.objects.create(
|
||
kit=new_kit,
|
||
product=item.product,
|
||
variant_group=item.variant_group,
|
||
quantity=item.quantity
|
||
)
|
||
|
||
# Пересчитываем цену
|
||
new_kit.recalculate_base_price()
|
||
|
||
return new_kit
|