🔥 КРИТИЧЕСКОЕ ИСПРАВЛЕНИЕ: Дублирование резервов при изменении количества
Проблема: - При изменении количества в OrderItem для заказа в статусе 'completed' - Создавался ДУБЛИКАТ резерва (старый converted_to_sale + новый reserved) - Это приводило к двойному списанию товара со склада (-10 лишних единиц) - Фильтр status='reserved' пропускал существующие резервы в других статусах Сценарий бага: 1. Заказ выполнен: 20 шт → резерв 20 шт (converted_to_sale) 2. Увеличить на 10 шт (до 30) → создаётся НОВЫЙ резерв 30 шт (reserved) 3. Итого: 20 + 30 = 50 шт зарезервировано вместо 30! 4. При переводе обратно в 'completed' → двойное списание (50 вместо 30) Решение: - Убран фильтр status='reserved' из update_reservation_on_item_change - Теперь резерв ищется по order_item независимо от статуса - Обновляется ТОЛЬКО quantity, статус НЕ меняется - Добавлен @transaction.atomic для атомарности операции - Добавлено логирование всех операций с резервами - Используется save(update_fields=['quantity']) для оптимизации Безопасность решения: - Резервы разных заказов НЕ конфликтуют (разные order_item) - Один товар в разных заказах = разные OrderItem = разные Reservation - Каждый OrderItem имеет уникальный резерв - Дубликаты больше НЕ создаются Изменённые файлы: - inventory/signals.py (функция update_reservation_on_item_change) - FIX_RESERVATION_DUPLICATE_BUG.md (полная документация бага и решения) Покрытие всех сценариев: ✅ Создание заказа с товарами ✅ Добавление товара при редактировании ✅ Изменение количества (черновик) ✅ Изменение количества (выполнен) - ИСПРАВЛЕНО ✅ Повторное сохранение заказа КРИТИЧНО: Это исправление влияет на учёт товара и требует тестирования!
This commit is contained in:
@@ -434,30 +434,45 @@ def release_stock_on_order_delete(sender, instance, **kwargs):
|
||||
|
||||
|
||||
@receiver(post_save, sender=OrderItem)
|
||||
@transaction.atomic
|
||||
def update_reservation_on_item_change(sender, instance, created, **kwargs):
|
||||
"""
|
||||
Сигнал: При создании или изменении позиции заказа управляем резервами.
|
||||
|
||||
Процесс:
|
||||
1. Ищем существующий резерв для этой позиции
|
||||
2. Если резерв ЕСТЬ - обновляем количество
|
||||
1. Ищем существующий резерв для этой позиции (в ЛЮБОМ статусе)
|
||||
2. Если резерв ЕСТЬ - обновляем ТОЛЬКО количество (статус НЕ меняем!)
|
||||
3. Если резерва НЕТ - создаем новый
|
||||
|
||||
Покрывает все сценарии:
|
||||
- Создание заказа с товарами → создаёт резервы
|
||||
- Редактирование + добавление товаров → создаёт резервы для новых
|
||||
- Изменение количества → обновляет резервы
|
||||
- Изменение количества → обновляет резервы (даже если уже converted_to_sale)
|
||||
|
||||
КРИТИЧНО: Убран фильтр status='reserved' чтобы избежать дубликатов!
|
||||
Резерв ищется по order_item независимо от статуса.
|
||||
Это предотвращает создание нового резерва для заказа в статусе 'completed'.
|
||||
"""
|
||||
# Получаем резерв для этой позиции в статусе 'reserved'
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Ищем резерв для этой позиции в ЛЮБОМ статусе (не только 'reserved')
|
||||
# КРИТИЧНО: Убран фильтр status='reserved' для предотвращения дубликатов
|
||||
reservation = Reservation.objects.filter(
|
||||
order_item=instance,
|
||||
status='reserved'
|
||||
order_item=instance
|
||||
).first()
|
||||
|
||||
if reservation:
|
||||
# Резерв существует - обновляем его количество
|
||||
# Резерв существует - обновляем ТОЛЬКО количество
|
||||
# НЕ меняем статус! (может быть 'converted_to_sale', 'reserved', 'released')
|
||||
old_quantity = reservation.quantity
|
||||
reservation.quantity = Decimal(str(instance.quantity))
|
||||
reservation.save()
|
||||
reservation.save(update_fields=['quantity'])
|
||||
|
||||
logger.info(
|
||||
f"✓ Резерв #{reservation.id} обновлён: quantity {old_quantity} → {reservation.quantity} "
|
||||
f"(статус: {reservation.status}, OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
else:
|
||||
# Резерва нет - создаем новый
|
||||
# Это происходит при создании нового OrderItem (через форму или при редактировании)
|
||||
@@ -467,13 +482,18 @@ def update_reservation_on_item_change(sender, instance, created, **kwargs):
|
||||
product = instance.product if instance.product else instance.product_kit
|
||||
|
||||
if product:
|
||||
Reservation.objects.create(
|
||||
reservation = Reservation.objects.create(
|
||||
order_item=instance,
|
||||
product=product,
|
||||
warehouse=warehouse,
|
||||
quantity=Decimal(str(instance.quantity)),
|
||||
status='reserved'
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"✓ Создан новый резерв #{reservation.id}: {product.name}, quantity={reservation.quantity} "
|
||||
f"(OrderItem #{instance.id}, заказ {instance.order.order_number})"
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Incoming)
|
||||
|
||||
Reference in New Issue
Block a user