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

Проблема #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 - полная документация
This commit is contained in:
2025-12-01 01:10:58 +03:00
parent e0437cdb5a
commit 293e8640ef
2 changed files with 260 additions and 20 deletions

View File

@@ -390,41 +390,51 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
@receiver(pre_delete, sender=Order)
@transaction.atomic
def release_stock_on_order_delete(sender, instance, **kwargs):
"""
Сигнал: При удалении/отмене заказа освободить резервы.
Процесс:
1. Ищем все резервы для этого заказа
2. Меняем статус резерва на 'released'
3. Фиксируем время освобождения
1. Ищем все резервы для этого заказа ДО удаления
2. Освобождаем резервы ПОСЛЕ успешного коммита транзакции
3. Это гарантирует, что резервы освободятся только если удаление успешно
"""
# Находим все резервы для этого заказа
reservations = Reservation.objects.filter(
order_item__order=instance,
status='reserved'
# Находим все резервы для этого заказа ДО удаления
# Используем list() чтобы выполнить запрос сейчас, пока Order ещё существует
reservations_to_release = list(
Reservation.objects.filter(
order_item__order=instance,
status='reserved'
)
)
# Освобождаем каждый резерв
for res in reservations:
res.status = 'released'
res.released_at = timezone.now()
res.save()
# Освобождаем резервы ПОСЛЕ успешного коммита транзакции
# Это гарантирует целостность: резервы освободятся только если удаление прошло успешно
def release_reservations():
for res in reservations_to_release:
res.status = 'released'
res.released_at = timezone.now()
res.save()
transaction.on_commit(release_reservations)
@receiver(post_save, sender=OrderItem)
def update_reservation_on_item_change(sender, instance, created, **kwargs):
"""
Сигнал: Если изменилось количество товара в позиции заказа,
обновить резерв.
Сигнал: При создании или изменении позиции заказа управляем резервами.
Процесс:
1. Если это новая позиция - игнорируем (резерв уже создан через Order)
2. Если изменилось количество - обновляем резерв или создаем новый
"""
if created:
return # Новые позиции обрабатываются через Order signal
1. Ищем существующий резерв для этой позиции
2. Если резерв ЕСТЬ - обновляем количество
3. Если резерва НЕТ - создаем новый
Покрывает все сценарии:
- Создание заказа с товарами → создаёт резервы
- Редактирование + добавление товаров → создаёт резервы для новых
- Изменение количества → обновляет резервы
"""
# Получаем резерв для этой позиции в статусе 'reserved'
reservation = Reservation.objects.filter(
order_item=instance,
@@ -432,11 +442,12 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs):
).first()
if reservation:
# Резерв существует - обновляем его
# Резерв существует - обновляем его количество
reservation.quantity = Decimal(str(instance.quantity))
reservation.save()
else:
# Резерва нет - создаем новый
# Это происходит при создании нового OrderItem (через форму или при редактировании)
warehouse = instance.order.pickup_warehouse or Warehouse.objects.filter(is_active=True).first()
if warehouse: