Реализована логика резервирования витринных букетов через сигналы

- 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:
2026-01-05 01:38:14 +03:00
parent 24a64edc82
commit dd37931f5e
2 changed files with 87 additions and 5 deletions

View File

@@ -397,10 +397,11 @@ class ShowcaseManager:
}
# Берём только актуальные экземпляры на витрине
# (available, in_cart, reserved — все физически присутствующие)
active_items = ShowcaseItem.objects.filter(
showcase=showcase,
product_kit=product_kit,
status__in=['available', 'in_cart'],
status__in=['available', 'in_cart', 'reserved'],
)
item_count = active_items.count()
@@ -488,7 +489,7 @@ class ShowcaseManager:
active_items = ShowcaseItem.objects.filter(
showcase=showcase,
product_kit=product_kit,
status__in=['available', 'in_cart'],
status__in=['available', 'in_cart', 'reserved'],
)
if not active_items.exists():

View File

@@ -494,6 +494,33 @@ def create_sale_on_order_completion(sender, instance, created, **kwargs):
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(
f"🎉 Заказ {instance.order_number} успешно обработан: создано {len(sales_created)} Sale, "
f"обновлено {reservations_to_update.count() if reservations_to_update.exists() else 0} резервов"
@@ -838,9 +865,36 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
f"{showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами"
)
else:
# Сценарий А: Возврат к нейтральному - ShowcaseItem ОСТАЁТСЯ sold
# Сценарий А: Возврат к нейтральному - ShowcaseItem sold → reserved
from inventory.models import ShowcaseItem
# Находим все 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" Сценарий А: Витринные экземпляры остаются в статусе 'sold' (заказ в нейтральном статусе)"
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 ===
@@ -972,6 +1026,33 @@ def release_reservations_on_cancellation(sender, instance, created, **kwargs):
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 ===
# Используем единую функцию для обновления флага
update_is_returned_flag(instance)