Files
octopus/RESERVATION_FIX.md
Andrey Smakotin 293e8640ef Исправлено создание резервов при сохранении заказов
Проблема #1: Резервы не создавались при создании заказа
- Order сохранялся БЕЗ items → сигнал reserve_stock_on_order_create
  не мог создать резервы (items.all() был пустой)
- OrderItem создавались ПОСЛЕ, но сигнал уже отработал

Решение #1: Создание резервов через сигнал OrderItem
- Доработан сигнал update_reservation_on_item_change
- Убрано раннее возвращение для created=True
- Теперь резервы создаются при добавлении OrderItem (любым способом)
- Работает для всех сценариев:
  * Создание заказа с товарами
  * Добавление товаров при редактировании
  * Изменение количества

Проблема #2: Риск расхождений при удалении заказа
- Сигнал pre_delete освобождал резервы ДО удаления
- Если удаление падало с ошибкой → резервы освобождены, Order не удалён
- Возникало расхождение данных

Решение #2: transaction.on_commit для освобождения резервов
- Добавлен @transaction.atomic к сигналу release_stock_on_order_delete
- Резервы получаются ДО удаления через list()
- Освобождение происходит через transaction.on_commit()
- Резервы освобождаются ТОЛЬКО если удаление успешно
- Гарантия целостности данных

Изменённые файлы:
- myproject/inventory/signals.py - оба сигнала исправлены
- RESERVATION_FIX.md - полная документация
2025-12-01 01:10:58 +03:00

10 KiB
Raw Blame History

Исправление создания резервов для заказов

🐛 Проблема

При создании нового заказа со статусом "Черновик" (или любым другим) резервы товаров не создавались.

Причина

Неправильный порядок сохранения в orders/views.py:

def order_create(request):
    # ...
    order.save()      # ← 1. Сигнал reserve_stock_on_order_create срабатывает
                      #    НО instance.items.all() пустой!
    
    formset.save()    # ← 2. OrderItem создаются ПОСЛЕ
                      #    Сигнал больше не сработает

Что происходило:

  1. order.save() → сигнал reserve_stock_on_order_create срабатывал
  2. Сигнал проверял instance.items.all()ПУСТО (OrderItem ещё не созданы)
  3. Цикл for item in instance.items.all() не выполнялся
  4. Резервы НЕ создавались
  5. formset.save() → OrderItem создавались, но сигнал уже отработал

Решение #1: Создание резервов через сигнал OrderItem

Изменённый файл: inventory/signals.py

Сигнал: update_reservation_on_item_change (строки 415-452)

Было (ОШИБОЧНО):

@receiver(post_save, sender=OrderItem)
def update_reservation_on_item_change(sender, instance, created, **kwargs):
    if created:
        return  # ❌ Новые позиции игнорировались!
    
    # Код обновления резервов...

Стало (ИСПРАВЛЕНО):

@receiver(post_save, sender=OrderItem)
def update_reservation_on_item_change(sender, instance, created, **kwargs):
    """
    При создании или изменении позиции заказа управляем резервами.
    
    Покрывает все сценарии:
    - Создание заказа с товарами → создаёт резервы
    - Редактирование + добавление товаров → создаёт резервы для новых
    - Изменение количества → обновляет резервы
    """
    # ✅ Убрали раннее возвращение для created=True
    
    reservation = Reservation.objects.filter(
        order_item=instance,
        status='reserved'
    ).first()
    
    if reservation:
        # Резерв существует - обновляем количество
        reservation.quantity = Decimal(str(instance.quantity))
        reservation.save()
    else:
        # Резерва нет - создаём новый
        warehouse = instance.order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
        
        if warehouse:
            product = instance.product if instance.product else instance.product_kit
            
            if product:
                Reservation.objects.create(
                    order_item=instance,
                    product=product,
                    warehouse=warehouse,
                    quantity=Decimal(str(instance.quantity)),
                    status='reserved'
                )

Ключевые изменения:

  1. Убрали if created: return - сигнал работает для ВСЕХ сохранений OrderItem
  2. Резервы создаются при добавлении OrderItem (независимо от способа создания)
  3. При редактировании только обновляются (если резерв уже есть)
  4. Работает для любого статуса заказа (Черновик, Новый, и т.д.)

Решение #2: Безопасное освобождение резервов при удалении

Изменённый файл: inventory/signals.py

Сигнал: release_stock_on_order_delete (строки 392-413)

Проблема с pre_delete:

Сценарий риска:

  1. Сигнал pre_delete срабатывает → резервы освобождаются
  2. Удаление Order ПАДАЕТ с ошибкой (например, из-за ограничений БД)
  3. Результат: Order не удалён, но резервы освобождены! Расхождение!

Было (РИСКОВАННО):

@receiver(pre_delete, sender=Order)
def release_stock_on_order_delete(sender, instance, **kwargs):
    reservations = Reservation.objects.filter(
        order_item__order=instance,
        status='reserved'
    )
    
    # ❌ Освобождаем резервы ДО удаления
    # Если удаление упадёт - резервы уже изменены!
    for res in reservations:
        res.status = 'released'
        res.released_at = timezone.now()
        res.save()

Стало (БЕЗОПАСНО):

@receiver(pre_delete, sender=Order)
@transaction.atomic
def release_stock_on_order_delete(sender, instance, **kwargs):
    """
    При удалении заказа освобождаем резервы ТОЛЬКО после успешного коммита.
    """
    # ✅ Получаем резервы ДО удаления (пока Order существует)
    # Используем list() чтобы выполнить запрос сейчас
    reservations_to_release = list(
        Reservation.objects.filter(
            order_item__order=instance,
            status='reserved'
        )
    )
    
    # ✅ Освобождаем резервы ПОСЛЕ успешного коммита
    def release_reservations():
        for res in reservations_to_release:
            res.status = 'released'
            res.released_at = timezone.now()
            res.save()
    
    transaction.on_commit(release_reservations)

Ключевые изменения:

  1. Добавлен @transaction.atomic - гарантирует транзакцию
  2. Используем list() для получения резервов ДО удаления
  3. Используем transaction.on_commit() - освобождаем резервы ТОЛЬКО если удаление успешно
  4. Гарантия целостности данных - нет риска расхождений

📊 Покрытие всех сценариев

Сценарий OrderItem created Резерв существует? Действие
Создание заказа с товарами Новый True Нет Создать резерв
Создание заказа без товаров - - - Ничего
Редактирование: добавить товар Новый True Нет Создать резерв
Редактирование: изменить количество Старый False Да Обновить резерв
Повторное сохранение заказа Старый False Да Обновить резерв
Удаление заказа (успешно) - - - Освободить резервы
Удаление заказа (с ошибкой) - - - Резервы НЕ освобождаются

🎯 Преимущества решения

Решение #1 (OrderItem сигнал):

  • Универсальность - работает для любого способа создания OrderItem
  • Простота - минимальное изменение существующего сигнала
  • Независимость от статуса - резервы создаются для любого статуса заказа
  • Гибкость - можно добавлять товары при редактировании

Решение #2 (transaction.on_commit):

  • Целостность данных - резервы освобождаются только при успешном удалении
  • Безопасность - нет риска расхождений при ошибках
  • Прозрачность - очевидная логика работы
  • Надёжность - гарантии через транзакции

🧪 Как проверить

Тест 1: Создание заказа с товарами

  1. Создайте новый заказ со статусом "Черновик"
  2. Добавьте 2 товара по 10 штук каждый
  3. Сохраните заказ
  4. Проверка: В таблице Reservation должно быть 2 записи со status='reserved'

Тест 2: Создание заказа без товаров

  1. Создайте новый заказ без товаров
  2. Сохраните заказ
  3. Проверка: Резервы не создались (это нормально)

Тест 3: Добавление товара при редактировании

  1. Откройте существующий заказ
  2. Добавьте новый товар (5 штук)
  3. Сохраните заказ
  4. Проверка: Создался новый резерв для добавленного товара

Тест 4: Изменение количества

  1. Откройте существующий заказ с товарами
  2. Измените количество товара с 10 на 15
  3. Сохраните заказ
  4. Проверка: Резерв обновился (quantity=15)

Тест 5: Удаление заказа

  1. Удалите заказ с товарами
  2. Проверка: Резервы переведены в status='released'
  3. Попробуйте удалить заказ с нарушением ограничений БД
  4. Проверка: Если удаление упало - резервы НЕ изменились

📝 Связанные файлы

  • myproject/inventory/signals.py - оба исправления
  • myproject/orders/views.py - порядок сохранения (не изменялся)

Дата исправления: 2024-12-01