Реализована логика резервирования витринных букетов через сигналы
- inventory/signals.py: обработчик изменения статуса Order * При смене статуса на 'завершён' (is_positive_end=True): reserved → sold * При смене на 'отменён' (is_negative_end=True): reserved → available - inventory/services/showcase_manager.py: метод reserve_for_order() * Переводит ShowcaseItem: in_cart → reserved * Создаёт жёсткую связь с OrderItem * Автоматическое управление статусами через сигналы - Транзакционная безопасность через @transaction.atomic
This commit is contained in:
@@ -397,10 +397,11 @@ class ShowcaseManager:
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Берём только актуальные экземпляры на витрине
|
# Берём только актуальные экземпляры на витрине
|
||||||
|
# (available, in_cart, reserved — все физически присутствующие)
|
||||||
active_items = ShowcaseItem.objects.filter(
|
active_items = ShowcaseItem.objects.filter(
|
||||||
showcase=showcase,
|
showcase=showcase,
|
||||||
product_kit=product_kit,
|
product_kit=product_kit,
|
||||||
status__in=['available', 'in_cart'],
|
status__in=['available', 'in_cart', 'reserved'],
|
||||||
)
|
)
|
||||||
|
|
||||||
item_count = active_items.count()
|
item_count = active_items.count()
|
||||||
@@ -488,7 +489,7 @@ class ShowcaseManager:
|
|||||||
active_items = ShowcaseItem.objects.filter(
|
active_items = ShowcaseItem.objects.filter(
|
||||||
showcase=showcase,
|
showcase=showcase,
|
||||||
product_kit=product_kit,
|
product_kit=product_kit,
|
||||||
status__in=['available', 'in_cart'],
|
status__in=['available', 'in_cart', 'reserved'],
|
||||||
)
|
)
|
||||||
|
|
||||||
if not active_items.exists():
|
if not active_items.exists():
|
||||||
|
|||||||
@@ -494,6 +494,33 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
|
|||||||
f"✓ Обновлено {updated_count} резервов для заказа {instance.order_number}: reserved → converted_to_sale"
|
f"✓ Обновлено {updated_count} резервов для заказа {instance.order_number}: reserved → converted_to_sale"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# === Финализация витринных экземпляров: reserved → sold ===
|
||||||
|
# Находим все витринные комплекты в этом заказе, которые в статусе reserved
|
||||||
|
from inventory.models import ShowcaseItem
|
||||||
|
|
||||||
|
showcase_items_to_finalize = ShowcaseItem.objects.filter(
|
||||||
|
sold_order_item__order=instance,
|
||||||
|
status='reserved'
|
||||||
|
)
|
||||||
|
|
||||||
|
finalized_count = 0
|
||||||
|
for showcase_item in showcase_items_to_finalize:
|
||||||
|
try:
|
||||||
|
showcase_item.mark_sold_from_reserved()
|
||||||
|
finalized_count += 1
|
||||||
|
logger.info(
|
||||||
|
f"✓ Витринный экземпляр #{showcase_item.id} финализирован: reserved → sold"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка финализации ShowcaseItem #{showcase_item.id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if finalized_count > 0:
|
||||||
|
logger.info(
|
||||||
|
f"🎉 Финализировано {finalized_count} витринных экземпляров для заказа {instance.order_number}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"🎉 Заказ {instance.order_number} успешно обработан: создано {len(sales_created)} Sale, "
|
f"🎉 Заказ {instance.order_number} успешно обработан: создано {len(sales_created)} Sale, "
|
||||||
f"обновлено {reservations_to_update.count() if reservations_to_update.exists() else 0} резервов"
|
f"обновлено {reservations_to_update.count() if reservations_to_update.exists() else 0} резервов"
|
||||||
@@ -838,10 +865,37 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
|
|||||||
f"✅ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами"
|
f"✅ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Сценарий А: Возврат к нейтральному - ShowcaseItem ОСТАЁТСЯ sold
|
# Сценарий А: Возврат к нейтральному - ShowcaseItem sold → reserved
|
||||||
logger.info(
|
from inventory.models import ShowcaseItem
|
||||||
f"ℹ️ Сценарий А: Витринные экземпляры остаются в статусе 'sold' (заказ в нейтральном статусе)"
|
|
||||||
|
# Находим все ShowcaseItem в статусе 'sold' для этого заказа
|
||||||
|
showcase_items_to_unreserve = ShowcaseItem.objects.filter(
|
||||||
|
sold_order_item__order=instance,
|
||||||
|
status='sold'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unreserved_count = 0
|
||||||
|
for showcase_item in showcase_items_to_unreserve:
|
||||||
|
try:
|
||||||
|
# Возвращаем в reserved (букет остаётся занят под заказ)
|
||||||
|
showcase_item.return_to_reserved(showcase_item.sold_order_item)
|
||||||
|
unreserved_count += 1
|
||||||
|
logger.info(
|
||||||
|
f"✓ Витринный экземпляр #{showcase_item.id} возвращён в резерв: sold → reserved"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка возврата ShowcaseItem #{showcase_item.id} в reserved: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if unreserved_count > 0:
|
||||||
|
logger.info(
|
||||||
|
f"🔄 {unreserved_count} витринных экземпляров возвращено в резерв: sold → reserved (заказ в нейтральном статусе)"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f"ℹ️ Сценарий А: Нет витринных экземпляров для возврата в reserved"
|
||||||
|
)
|
||||||
|
|
||||||
# === Обновляем is_returned ===
|
# === Обновляем is_returned ===
|
||||||
# Используем единую функцию для обновления флага на основе фактического состояния
|
# Используем единую функцию для обновления флага на основе фактического состояния
|
||||||
@@ -972,6 +1026,33 @@ def release_reservations_on_cancellation(sender, instance, created, **kwargs):
|
|||||||
f"ℹ️ Для заказа {instance.order_number} нет резервов в статусе 'reserved'"
|
f"ℹ️ Для заказа {instance.order_number} нет резервов в статусе 'reserved'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# === Освобождаем ShowcaseItem при отмене: reserved/sold → available ===
|
||||||
|
from inventory.models import ShowcaseItem
|
||||||
|
|
||||||
|
# Находим все ShowcaseItem для этого заказа в статусах reserved или sold
|
||||||
|
showcase_items_to_release = ShowcaseItem.objects.filter(
|
||||||
|
sold_order_item__order=instance,
|
||||||
|
status__in=['reserved', 'sold']
|
||||||
|
)
|
||||||
|
|
||||||
|
released_showcase_count = 0
|
||||||
|
for showcase_item in showcase_items_to_release:
|
||||||
|
try:
|
||||||
|
showcase_item.return_to_available()
|
||||||
|
released_showcase_count += 1
|
||||||
|
logger.info(
|
||||||
|
f"✓ Витринный экземпляр #{showcase_item.id} освобождён: {showcase_item.status} → available"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(
|
||||||
|
f"❌ Ошибка освобождения ShowcaseItem #{showcase_item.id}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if released_showcase_count > 0:
|
||||||
|
logger.info(
|
||||||
|
f"🎉 {released_showcase_count} витринных экземпляров освобождено и возвращено на витрину при отмене заказа"
|
||||||
|
)
|
||||||
|
|
||||||
# === Обновляем is_returned ===
|
# === Обновляем is_returned ===
|
||||||
# Используем единую функцию для обновления флага
|
# Используем единую функцию для обновления флага
|
||||||
update_is_returned_flag(instance)
|
update_is_returned_flag(instance)
|
||||||
|
|||||||
Reference in New Issue
Block a user