diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 6fe3eb9..b4733a8 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -498,6 +498,94 @@ def release_reservations_on_cancellation(sender, instance, created, **kwargs): ) +@receiver(post_save, sender=Order) +@transaction.atomic +def reserve_stock_on_uncancellation(sender, instance, created, **kwargs): + """ + Сигнал: Резервирование товара при переходе ОТ статуса 'cancelled' к другим статусам. + + Триггер: cancelled → любой НЕ отменённый статус (draft, pending, completed и т.д.) + + Процесс: + 1. Проверяем что предыдущий статус был 'cancelled' (is_negative_end) + 2. Проверяем что текущий статус НЕ 'cancelled' + 3. Находим резервы в статусе 'released' + 4. Переводим их обратно в 'reserved' + 5. Stock автоматически обновится через сигнал + + ПРИМЕРЫ сценариев: + - cancelled → pending: резервы 'released' → 'reserved' ✅ + - cancelled → draft: резервы 'released' → 'reserved' ✅ + - cancelled → completed: резервы 'released' → 'reserved', затем create_sale_on_order_completion обработает ✅ + """ + import logging + logger = logging.getLogger(__name__) + + # Пропускаем новые заказы + if created: + return + + # Проверяем наличие статуса + if not instance.status: + return + + current_status = instance.status + + # Проверяем: текущий статус НЕ отмена? + if current_status.is_negative_end: + return # Всё ещё в отмене, выходим + + # === Получаем предыдущий статус === + try: + history_count = instance.history.count() + if history_count < 2: + return # Нет истории для сравнения + + previous_record = instance.history.all()[1] + + if not previous_record.status_id: + return + + from orders.models import OrderStatus + previous_status = OrderStatus.objects.get(id=previous_record.status_id) + + except (instance.history.model.DoesNotExist, OrderStatus.DoesNotExist, IndexError): + return + + # Проверяем: был ли предыдущий статус = cancelled? + if not previous_status.is_negative_end: + return # Не было перехода от cancelled, выходим + + # === Резервируем товар заново === + # Ищем резервы в статусе 'released' + reservations = Reservation.objects.filter( + order_item__order=instance, + status='released' + ) + + reservations_count = reservations.count() + + if reservations_count > 0: + logger.info( + f"🔄 Переход от статуса '{previous_status.name}' к '{current_status.name}' для заказа {instance.order_number}. " + f"Резервируем {reservations_count} освобождённых резервов..." + ) + + # Обновляем резервы через .save() чтобы сработал сигнал обновления Stock + for reservation in reservations: + reservation.status = 'reserved' + reservation.reserved_at = timezone.now() # Обновляем время резервирования + reservation.save(update_fields=['status', 'reserved_at']) + + logger.info( + f"✅ Зарезервировано {reservations_count} резервов: released → reserved" + ) + else: + logger.debug( + f"ℹ️ Для заказа {instance.order_number} нет резервов в статусе 'released'" + ) + + @receiver(pre_delete, sender=Order) @transaction.atomic def release_stock_on_order_delete(sender, instance, **kwargs):