Files
octopus/myproject/products/services/kit_service.py
Andrey Smakotin 08a5527ba7 Fix cart lock validation and error handling improvements
## 1. Add cart lock validation to sell_from_showcase()
- Prevent selling showcase kits locked in another cashier's cart
- Check cart_lock_expires_at before allowing direct sales
- Return clear error message with lock holder's name and time remaining
- File: inventory/services/showcase_manager.py

## 2. Improve error handling in POS create_temp_kit_to_showcase()
- Add detailed logging for all error types (JSON, validation, generic)
- Provide user-friendly error messages instead of generic 500
- Log full context (kit name, showcase ID, items, user) for debugging
- Categorize errors: stock issues, integrity, locks, not found
- File: pos/views.py

## 3. Fix critical bug in create_temporary_kit()
- Replace non-existent is_active field with status='active'
- Affects 3 locations: kit creation, product lookup, kit duplication
- This was causing 500 errors when creating temporary kits from order edit
- File: products/services/kit_service.py

## 4. Improve error handling in create_temporary_kit_api()
- Add comprehensive logging for order creation endpoint
- Provide specific error messages for common failure scenarios
- Help diagnose issues when creating kits from order editing UI
- File: products/views/api_views.py

These changes complete the Soft Lock system and fix the 500 error issue.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 00:24:59 +03:00

160 lines
5.8 KiB
Python
Raw 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()
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