From 0faae69c6394eb00cce99f2745ec42aebe614815 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 5 Jan 2026 09:41:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20Showca?= =?UTF-8?q?seItem=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5=20cancelled=20=E2=86=92=20completed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: - При переходе cancelled → completed срабатывали ОБА сигнала: 1. reserve_stock_on_uncancellation переводил ShowcaseItem: available → reserved 2. create_sale_on_order_completion искал букеты в available, но они уже в reserved 3. Букеты оставались в reserved вместо sold Решение: - inventory/signals.py: в reserve_stock_on_uncancellation добавлена проверка current_status.is_positive_end - Если текущий статус положительный финальный (completed) - ShowcaseItem НЕ трогаем - Оставляем в available для финализации в create_sale_on_order_completion - Если текущий статус нейтральный (draft/pending) - переводим available → reserved как раньше Flow теперь работает корректно: 1. cancelled → draft/pending: ShowcaseItem available → reserved ✅ 2. cancelled → completed: ShowcaseItem available → sold ✅ (ИСПРАВЛЕНО!) - reserve_stock_on_uncancellation пропускает (видит is_positive_end) - create_sale_on_order_completion финализирует: available → sold Защита от race condition между двумя сигналами. --- myproject/inventory/signals.py | 62 ++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 92999fc..a309016 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -1236,37 +1236,47 @@ def reserve_stock_on_uncancellation(sender, instance, created, **kwargs): # === Возвращаем ShowcaseItem из available обратно в reserved === # При отмене (cancelled) ShowcaseItem переходит в 'available'. # При возврате к нейтральному статусу нужно вернуть в 'reserved'. + # НО: При переходе в положительный финальный статус - НЕ трогаем! + # (create_sale_on_order_completion сам переведёт available → sold) from inventory.models import ShowcaseItem - # Находим все ShowcaseItem которые были освобождены при отмене - # (у них sold_order_item сброшен в return_to_available, нужно найти через резервы) - for order_item in showcase_order_items: - kit = order_item.product_kit - - # Находим ShowcaseItem этого комплекта в статусе 'available' - # Их sold_order_item = None, поэтому ищем через product_kit - available_items = ShowcaseItem.objects.filter( - product_kit=kit, - status='available', - sold_order_item__isnull=True + # Проверяем: если текущий статус положительный финальный, то пропускаем + if current_status.is_positive_end: + logger.info( + f"🔄 Переход к положительному финальному статусу '{current_status.name}'. " + f"ShowcaseItem остаются в 'available' для финализации в create_sale_on_order_completion." ) - - if available_items.exists(): - logger.info( - f" 🔄 Найдено {available_items.count()} ShowcaseItem комплекта '{kit.name}' в статусе 'available'. " - f"Возвращаем в reserved..." + else: + # Переход к нейтральному статусу - возвращаем в reserved + # Находим все ShowcaseItem которые были освобождены при отмене + # (у них sold_order_item сброшен в return_to_available, нужно найти через резервы) + for order_item in showcase_order_items: + kit = order_item.product_kit + + # Находим ShowcaseItem этого комплекта в статусе 'available' + # Их sold_order_item = None, поэтому ищем через product_kit + available_items = ShowcaseItem.objects.filter( + product_kit=kit, + status='available', + sold_order_item__isnull=True ) - for item in available_items: - try: - item.return_to_reserved(order_item) - logger.info( - f" ✅ ShowcaseItem #{item.id}: available → reserved (привязан к OrderItem #{order_item.id})" - ) - except Exception as e: - logger.error( - f" ❌ Ошибка возврата ShowcaseItem #{item.id} в reserved: {e}" - ) + if available_items.exists(): + logger.info( + f" 🔄 Найдено {available_items.count()} ShowcaseItem комплекта '{kit.name}' в статусе 'available'. " + f"Возвращаем в reserved..." + ) + + for item in available_items: + try: + item.return_to_reserved(order_item) + logger.info( + f" ✅ ShowcaseItem #{item.id}: available → reserved (привязан к OrderItem #{order_item.id})" + ) + except Exception as e: + logger.error( + f" ❌ Ошибка возврата ShowcaseItem #{item.id} в reserved: {e}" + ) @receiver(pre_delete, sender=Order)