Fixed critical bug: release reservations on draft->cancelled transition
This commit is contained in:
@@ -201,6 +201,9 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
|
||||
Сценарии:
|
||||
- А (ошибка): completed → draft/in_delivery → резервы возвращаются в 'reserved'
|
||||
- Б (отмена): completed → cancelled → резервы освобождаются в 'released'
|
||||
|
||||
ПРИМЕЧАНИЕ: Этот сигнал ОБРАБАТЫВАЕТ ТОЛЬКО переход ОТ 'completed'!
|
||||
Для перехода к 'cancelled' из любого статуса см. release_reservations_on_cancellation
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -402,6 +405,99 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Order)
|
||||
@transaction.atomic
|
||||
def release_reservations_on_cancellation(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Сигнал: Освобождение резервов при переходе К cancelled из ЛЮБОГО статуса.
|
||||
|
||||
Триггер: любой_статус → cancelled
|
||||
|
||||
Процесс:
|
||||
1. Проверяем что текущий статус = 'cancelled' (или is_negative_end)
|
||||
2. Проверяем что предыдущий статус НЕ 'cancelled' (чтобы избежать повторной обработки)
|
||||
3. Освобождаем все резервы в статусе 'reserved': status → 'released'
|
||||
4. Устанавливаем released_at
|
||||
|
||||
ПРИМЕРЫ сценариев:
|
||||
- draft → cancelled: резервы 'reserved' → 'released' ✅
|
||||
- pending → cancelled: резервы 'reserved' → 'released' ✅
|
||||
- completed → cancelled: обрабатывается rollback_sale_on_status_change ⚠️
|
||||
|
||||
ПРИМЕЧАНИЕ: Для completed → cancelled резервы в 'converted_to_sale',
|
||||
поэтому этот сигнал их не затронет (обрабатывает rollback_sale_on_status_change).
|
||||
"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Пропускаем новые заказы
|
||||
if created:
|
||||
return
|
||||
|
||||
# Проверяем наличие статуса
|
||||
if not instance.status:
|
||||
return
|
||||
|
||||
current_status = instance.status
|
||||
|
||||
# Проверяем: это статус отмены?
|
||||
if not current_status.is_negative_end:
|
||||
return # Не отмена, выходим
|
||||
|
||||
# === Получаем предыдущий статус ===
|
||||
try:
|
||||
history_count = instance.history.count()
|
||||
if history_count < 2:
|
||||
# Нет истории - значит заказ создан сразу в cancelled (необычно, но возможно)
|
||||
# Продолжаем обработку
|
||||
previous_status = None
|
||||
else:
|
||||
previous_record = instance.history.all()[1]
|
||||
|
||||
if not previous_record.status_id:
|
||||
previous_status = None
|
||||
else:
|
||||
from orders.models import OrderStatus
|
||||
previous_status = OrderStatus.objects.get(id=previous_record.status_id)
|
||||
|
||||
except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError):
|
||||
previous_status = None
|
||||
|
||||
# Проверяем: не был ли уже в cancelled?
|
||||
if previous_status and previous_status.is_negative_end:
|
||||
return # Уже был в отмене, не обрабатываем повторно
|
||||
|
||||
# === Освобождаем резервы ===
|
||||
# Ищем только резервы в статусе 'reserved'
|
||||
# Резервы в 'converted_to_sale' обрабатывает rollback_sale_on_status_change
|
||||
reservations = Reservation.objects.filter(
|
||||
order_item__order=instance,
|
||||
status='reserved'
|
||||
)
|
||||
|
||||
reservations_count = reservations.count()
|
||||
|
||||
if reservations_count > 0:
|
||||
logger.info(
|
||||
f"🔄 Переход к статусу '{current_status.name}' для заказа {instance.order_number}. "
|
||||
f"Освобождаем {reservations_count} резервов..."
|
||||
)
|
||||
|
||||
# Обновляем резервы через .save() чтобы сработал сигнал обновления Stock
|
||||
for reservation in reservations:
|
||||
reservation.status = 'released'
|
||||
reservation.released_at = timezone.now()
|
||||
reservation.save(update_fields=['status', 'released_at'])
|
||||
|
||||
logger.info(
|
||||
f"✅ Освобождено {reservations_count} резервов: reserved → released"
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
f"ℹ️ Для заказа {instance.order_number} нет резервов в статусе 'reserved'"
|
||||
)
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=Order)
|
||||
@transaction.atomic
|
||||
def release_stock_on_order_delete(sender, instance, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user