Исправлены баги витринных комплектов: резервы и валидация восстановления заказов

This commit is contained in:
2026-01-04 22:53:53 +03:00
parent 595cf6a018
commit 8041ceb04a
3 changed files with 224 additions and 64 deletions

View File

@@ -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)