From dd37931f5e1f0df6981192947e1e0656ad7cb1ed Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 5 Jan 2026 01:38:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B7=D0=B5=D1=80=D0=B2=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D0=B1=D1=83=D0=BA=D0=B5=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D1=81=D0=B8=D0=B3=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../inventory/services/showcase_manager.py | 5 +- myproject/inventory/signals.py | 87 ++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/myproject/inventory/services/showcase_manager.py b/myproject/inventory/services/showcase_manager.py index 1b4f75c..201c916 100644 --- a/myproject/inventory/services/showcase_manager.py +++ b/myproject/inventory/services/showcase_manager.py @@ -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(): diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 8f87147..3a1c18c 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -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,10 +865,37 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): f"✅ {showcase_items_count} витринных экземпляров вернулись на витрину: sold → available со связью с резервами" ) else: - # Сценарий А: Возврат к нейтральному - ShowcaseItem ОСТАЁТСЯ sold - logger.info( - f"ℹ️ Сценарий А: Витринные экземпляры остаются в статусе '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"✓ Витринный экземпляр #{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)