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

230 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Исправление создания резервов для заказов
## 🐛 Проблема
При создании нового заказа со статусом "Черновик" (или любым другим) резервы товаров **не создавались**.
### Причина
**Неправильный порядок сохранения в `orders/views.py`:**
```python
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)
### Было (ОШИБОЧНО):
```python
@receiver(post_save, sender=OrderItem)
def update_reservation_on_item_change(sender, instance, created, **kwargs):
if created:
return # ❌ Новые позиции игнорировались!
# Код обновления резервов...
```
### Стало (ИСПРАВЛЕНО):
```python
@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 не удалён, но резервы освобождены! ❌ Расхождение!
### Было (РИСКОВАННО):
```python
@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()
```
### Стало (БЕЗОПАСНО):
```python
@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