diff --git a/myproject/inventory/services/showcase_manager.py b/myproject/inventory/services/showcase_manager.py index 7ffe3f4..1b4f75c 100644 --- a/myproject/inventory/services/showcase_manager.py +++ b/myproject/inventory/services/showcase_manager.py @@ -180,7 +180,7 @@ class ShowcaseManager: ) for showcase_item in showcase_items_locked: - # Проверка статуса перед продажей + # Проверяем статус перед продажей if showcase_item.status == 'sold': raise ValidationError( f'Экземпляр "{showcase_item}" уже продан' @@ -200,6 +200,11 @@ class ShowcaseManager: ) for reservation in reservations: + # Проверяем что резерв ещё НЕ обработан + if reservation.status == 'converted_to_sale': + # Этот резерв уже преобразован в продажу, пропускаем + continue + # Сначала устанавливаем order_item для правильного определения цены reservation.order_item = order_item reservation.save() diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index c297adb..49bf505 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -132,8 +132,9 @@ def reserve_stock_on_item_create(sender, instance, created, **kwargs): 1. Проверяем, новая ли позиция (создана только что) 2. Для обычных товаров - создаём резерв с учетом единиц продажи 3. Для комплектов - резервируем компоненты (группируя одинаковые товары) - 4. Статус резерва = 'reserved' - 5. Проверяем на существующие резервы (защита от дубликатов) + 4. Для ВИТРИННЫХ комплектов - НЕ создаём резервы (они уже есть от ShowcaseItem) + 5. Статус резерва = 'reserved' + 6. Проверяем на существующие резервы (защита от дубликатов) """ from collections import defaultdict @@ -163,7 +164,22 @@ def reserve_stock_on_item_create(sender, instance, created, **kwargs): ) elif instance.product_kit and instance.kit_snapshot: - # Комплект - резервируем КОМПОНЕНТЫ из снимка + # КРИТИЧНО: Проверяем витринный ли это комплект + is_showcase_kit = instance.product_kit.is_temporary and instance.product_kit.showcase + + if is_showcase_kit: + # Витринный комплект - резервы УЖЕ созданы через ShowcaseManager.reserve_kit_to_showcase + # Привязка резервов к OrderItem происходит в update_reservation_on_item_change + # НЕ создаём новые резервы! + import logging + logger = logging.getLogger(__name__) + logger.info( + f"ℹ️ Витринный комплект '{instance.product_kit.name}': пропускаем создание резервов " + f"(уже созданы ShowcaseManager), OrderItem #{instance.id}" + ) + return + + # Обычный (постоянный) комплект - резервируем КОМПОНЕНТЫ из снимка # Группируем одинаковые товары для создания одного резерва product_quantities = defaultdict(Decimal) @@ -350,9 +366,21 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs): ).exclude(status='converted_to_sale') if not kit_reservations.exists(): - logger.warning( - f"⚠ Комплект '{kit.name}': не найдено резервов компонентов" - ) + # Проверяем, может быть витринный комплект уже продан через ShowcaseManager? + already_sold = Reservation.objects.filter( + order_item=item, + product_kit=kit, + status='converted_to_sale' + ).exists() + + if already_sold: + logger.info( + f"ℹ️ Витринный комплект '{kit.name}': резервы уже обработаны через ShowcaseManager.sell_showcase_items" + ) + else: + logger.warning( + f"⚠ Комплект '{kit.name}': не найдено резервов компонентов" + ) continue # Создаем Sale для каждого компонента комплекта @@ -728,20 +756,20 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): # === Возвращаем витринные экземпляры обратно на витрину === from inventory.models import ShowcaseItem - + # Находим все ShowcaseItem, проданные в рамках этого заказа showcase_items = ShowcaseItem.objects.filter( sold_order_item__order=instance, status='sold' ) - + showcase_items_count = showcase_items.count() - + if showcase_items_count > 0: logger.info( f"🔄 Возвращаем {showcase_items_count} витринных экземпляров обратно на витрину..." ) - + # Возвращаем каждый экземпляр на витрину for item in showcase_items: item.status = 'available' @@ -749,9 +777,31 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): item.sold_at = None # showcase и product_kit не трогаем - букет остаётся на той же витрине item.save(update_fields=['status', 'sold_order_item', 'sold_at', 'updated_at']) - + + # КРИТИЧНО: Восстанавливаем связь между ShowcaseItem и Reservation + # Находим все резервы этого комплекта для данного OrderItem + order_item = item.sold_order_item if hasattr(item, '_original_sold_order_item') else None + if not order_item: + # Пытаемся найти через заказ и product_kit + order_items = instance.items.filter(product_kit=item.product_kit) + if order_items.exists(): + order_item = order_items.first() + + if order_item: + # Восстанавливаем связь showcase_item в резервах + reservations_updated = Reservation.objects.filter( + order_item=order_item, + product_kit=item.product_kit, + status='reserved' + ).update(showcase_item=item) + + if reservations_updated > 0: + logger.debug( + f" ✅ Восстановлена связь для {reservations_updated} резервов ShowcaseItem #{item.id}" + ) + logger.info( - f"✓ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available" + f"✅ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами" ) # === Обновляем is_returned ===