From 2a3898fb4436f8516d9a8914c110c10806fb9146 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Thu, 11 Dec 2025 23:17:12 +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=D0=B0=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B4=D0=B0=D0=B6=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=80=D0=B8=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BB=D0=B5=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=20=D0=B2=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=20completed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: При отмене заказа (completed → cancelled) и последующем возврате в completed витринные комплекты оставались зарезервированными и не уходили со склада. Резервы не конвертировались в продажи, Sale не создавались. Причина: При откате заказа (уход от completed) мы обнуляли reservation.order_item = None для витринных комплектов. Это разрывало связь между резервом и позицией заказа. При повторном переходе в completed сигнал create_sale_on_order_completion искал резервы по фильтру: Reservation.objects.filter(order_item=item, product_kit=kit) Но так как order_item был None, резервы не находились и Sale не создавались. Решение: Разделили семантику полей Reservation: - order_item - принадлежность к позиции заказа (часть жизненного цикла заказа) - cart_lock_expires_at, locked_by_user, cart_session_id - блокировки корзины При откате заказа (completed → другой_статус): - НЕ трогаем order_item - он остаётся привязанным к OrderItem - Очищаем ТОЛЬКО cart lock поля (expires_at, locked_by_user, session_id) - Резервы витринных комплектов: status = reserved При повторном переходе в completed: - create_sale_on_order_completion находит резервы (order_item сохранён!) - Создаёт Sale для каждого компонента - Конвертирует резервы: reserved → converted_to_sale - Витринный экземпляр помечается как проданный через ShowcaseManager Изменения в 3 местах: 1. rollback_sale_on_status_change - откат от completed 2. release_reservations_on_cancellation - переход к cancelled 3. release_stock_on_order_delete - удаление заказа Во всех случаях для витринных комплектов сохраняем order_item, очищаем только cart lock поля. Результат: Теперь витринные комплекты можно продавать/отменять/продавать снова через смену статуса заказа в админке - как в реальной жизни. --- myproject/inventory/signals.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 2a17b6c..9efc690 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -456,18 +456,18 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs): if showcase_count > 0: for reservation in showcase_kit_reservations: reservation.status = 'reserved' - # Очищаем блокировки корзины при отмене заказа - reservation.order_item = None + # Очищаем ТОЛЬКО блокировки корзины при отмене заказа + # НЕ трогаем 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', 'order_item', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + 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 (возвращены на витрину, блокировки сняты)" + f"converted_to_sale → reserved (возвращены на витрину, блокировки сняты, order_item сохранён)" ) else: logger.warning( @@ -622,15 +622,15 @@ 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=['order_item', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + reservation.save(update_fields=['cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) logger.info( - f"ℹ️ Найдено {showcase_count} резервов витринных комплектов - остаются в reserved (на витрине, блокировки сняты)" + f"ℹ️ Найдено {showcase_count} резервов витринных комплектов - остаются в reserved (на витрине, блокировки сняты, order_item сохранён)" ) if normal_count == 0 and showcase_count == 0: @@ -792,13 +792,13 @@ def release_stock_on_order_delete(sender, instance, **kwargs): res.released_at = timezone.now() res.save() - # Витринные комплекты остаются зарезервированными, но отвязываем их от заказа и снимаем блокировки + # Витринные комплекты остаются зарезервированными, но отвязываем блокировки корзины + # НЕ трогаем order_item - он нужен если заказ снова перейдёт в completed for res in showcase_reservations: - res.order_item = None res.cart_lock_expires_at = None res.locked_by_user = None res.cart_session_id = None - res.save(update_fields=['order_item', 'cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) + res.save(update_fields=['cart_lock_expires_at', 'locked_by_user', 'cart_session_id']) transaction.on_commit(release_reservations)