Files
octopus/myproject/products/services/kit_service.py
Andrey Smakotin 2778796118 feat(pos): фиксировать цены товаров в витринных комплектах
- Добавлено поле KitItem.unit_price для хранения зафиксированной цены
- Витринные комплекты больше не обновляются при изменении цен товаров
- Добавлен красный индикатор на карточке если цена неактуальна
- Добавлен warning в модалке редактирования с кнопкой "Актуализировать"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 15:59:44 +03:00

164 lines
6.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сервис для работы с комплектами товаров.
Содержит бизнес-логику создания, обновления и управления комплектами.
"""
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,
status='active',
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, status='active')
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()
# Очищаем зафиксированные цены - теперь будет использоваться актуальная цена товаров
kit.kit_items.update(unit_price=None)
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, # Копия всегда постоянная
status=kit.status
)
# Копируем категории
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