Рефакторинг: перенос логики создания временных комплектов в сервис
Изменения: - Удалена функция 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>
This commit is contained in:
159
myproject/products/services/kit_service.py
Normal file
159
myproject/products/services/kit_service.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""
|
||||
Сервис для работы с комплектами товаров.
|
||||
Содержит бизнес-логику создания, обновления и управления комплектами.
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user