diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 49bf505..4e84a02 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -732,76 +732,102 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): f"converted_to_sale → {reservation_target_status}" ) - # Витринные временные комплекты ВСЕГДА возвращаются в reserved (остаются на витрине) + # Витринные временные комплекты: логика зависит от сценария if showcase_count > 0: - for reservation in showcase_kit_reservations: - reservation.status = 'reserved' - # Очищаем ТОЛЬКО блокировки корзины при отмене заказа - # НЕ трогаем order_item - он нужен для повторной продажи при возврате в completed - reservation.cart_lock_expires_at = None - reservation.locked_by_user = None - reservation.cart_session_id = None - # Не трогаем showcase и product_kit - они остаются привязанными - # converted_at оставляем (для истории) - reservation.save(update_fields=['status', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) - - logger.info( - f"✓ Обновлено {showcase_count} резервов витринных комплектов: " - f"converted_to_sale → reserved (возвращены на витрину, блокировки сняты, order_item сохранён)" - ) + if is_cancellation: + # Сценарий Б: Отмена - возвращаем на витрину + for reservation in showcase_kit_reservations: + reservation.status = 'reserved' + # КРИТИЧНО: Отвязываем резервы от заказа при отмене + reservation.order_item = None + # Очищаем блокировки корзины + reservation.cart_lock_expires_at = None + reservation.locked_by_user = None + reservation.cart_session_id = None + # showcase_item и product_kit остаются - букет на витрине + # converted_at оставляем (для истории) + reservation.save(update_fields=['status', 'order_item', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + + logger.info( + f"✓ Обновлено {showcase_count} резервов витринных комплектов: " + f"converted_to_sale → reserved (возвращены на витрину, отвязаны от заказа, блокировки сняты)" + ) + else: + # Сценарий А: Возврат к нейтральному - резервы ОСТАЮТСЯ в заказе + for reservation in showcase_kit_reservations: + reservation.status = 'reserved' + # Очищаем ТОЛЬКО блокировки корзины + # order_item НЕ ТРОГАЕМ - резерв остаётся за заказом! + reservation.cart_lock_expires_at = None + reservation.locked_by_user = None + reservation.cart_session_id = None + # converted_at оставляем (для истории) + reservation.save(update_fields=['status', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + + logger.info( + f"✓ Обновлено {showcase_count} резервов витринных комплектов: " + f"converted_to_sale → reserved (остаются в заказе, блокировки сняты)" + ) else: logger.warning( f"⚠ Для заказа {instance.order_number} нет резервов в статусе 'converted_to_sale'" ) # === Возвращаем витринные экземпляры обратно на витрину === - 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} витринных экземпляров обратно на витрину..." + # ТОЛЬКО при отмене (отрицательный статус)! + if is_cancellation: + from inventory.models import ShowcaseItem + + # Находим все ShowcaseItem, проданные в рамках этого заказа + showcase_items = ShowcaseItem.objects.filter( + sold_order_item__order=instance, + status='sold' ) - # Возвращаем каждый экземпляр на витрину - for item in showcase_items: - item.status = 'available' - item.sold_order_item = None - 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}" - ) + 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' + item.sold_order_item = None + 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 со связью с резервами" + ) + else: + # Сценарий А: Возврат к нейтральному - ShowcaseItem ОСТАЁТСЯ sold logger.info( - f"✅ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами" + f"ℹ️ Сценарий А: Витринные экземпляры остаются в статусе 'sold' (заказ в нейтральном статусе)" ) # === Обновляем is_returned === @@ -916,15 +942,16 @@ def release_reservations_on_cancellation(sender, instance, created, **kwargs): if showcase_count > 0: # Для витринных комплектов очищаем блокировки корзины - # НЕ трогаем order_item - он нужен для повторной продажи если статус вернётся в completed + # КРИТИЧНО: Отвязываем резервы от заказа при отмене for reservation in showcase_kit_reservations: + reservation.order_item = None reservation.cart_lock_expires_at = None reservation.locked_by_user = None reservation.cart_session_id = None - reservation.save(update_fields=['cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + reservation.save(update_fields=['order_item', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) logger.info( - f"ℹ️ Найдено {showcase_count} резервов витринных комплектов - остаются в reserved (на витрине, блокировки сняты, order_item сохранён)" + f"ℹ️ Найдено {showcase_count} резервов витринных комплектов - остаются в reserved (на витрине, отвязаны от заказа, блокировки сняты)" ) if normal_count == 0 and showcase_count == 0: @@ -996,7 +1023,7 @@ def reserve_stock_on_uncancellation(sender, instance, created, **kwargs): return # Не было перехода от cancelled, выходим # === Резервируем товар заново === - # Ищем резервы в статусе 'released' + # Ищем резервы в статусе 'released' (обычные резервы) reservations = Reservation.objects.filter( order_item__order=instance, status='released' @@ -1023,6 +1050,52 @@ def reserve_stock_on_uncancellation(sender, instance, created, **kwargs): logger.debug( f"ℹ️ Для заказа {instance.order_number} нет резервов в статусе 'released'" ) + + # === Привязываем витринные резервы обратно к заказу === + # Витринные резервы остаются в статусе 'reserved' при отмене, + # но отвязываются от order_item. При возврате нужно привязать их обратно. + + # Находим все OrderItem витринных комплектов в этом заказе + showcase_order_items = instance.items.filter( + product_kit__is_temporary=True, + product_kit__showcase__isnull=False + ).select_related('product_kit') + + showcase_items_count = showcase_order_items.count() + + if showcase_items_count > 0: + logger.info( + f"🔄 Найдено {showcase_items_count} витринных комплектов в заказе. " + f"Привязываем резервы обратно к заказу..." + ) + + for order_item in showcase_order_items: + kit = order_item.product_kit + + # Находим витринные резервы для этого комплекта + # (они в статусе 'reserved', но order_item=None) + showcase_reservations = Reservation.objects.filter( + product_kit=kit, + showcase__isnull=False, + status='reserved', + order_item__isnull=True + ) + + if showcase_reservations.exists(): + # Привязываем резервы обратно к OrderItem + updated_count = showcase_reservations.update(order_item=order_item) + + logger.info( + f" ✅ Витринный комплект '{kit.name}': привязано {updated_count} резервов к OrderItem #{order_item.id}" + ) + else: + logger.warning( + f" ⚠ Витринный комплект '{kit.name}': не найдено витринных резервов без order_item" + ) + + logger.info( + f"✅ Обработано {showcase_items_count} витринных комплектов" + ) @receiver(pre_delete, sender=Order) diff --git a/myproject/inventory/templates/inventory/debug_page.html b/myproject/inventory/templates/inventory/debug_page.html index fbcded1..19944dd 100644 --- a/myproject/inventory/templates/inventory/debug_page.html +++ b/myproject/inventory/templates/inventory/debug_page.html @@ -292,6 +292,7 @@