Исправлена повторная продажа витринных комплектов при возврате в статус completed
Проблема: При отмене заказа (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 поля. Результат: Теперь витринные комплекты можно продавать/отменять/продавать снова через смену статуса заказа в админке - как в реальной жизни.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user