Исправлена проблема с резервами при откате из статуса 'Выполнен'
Проблема: - При откате заказа из статуса 'completed' в 'возврат' или другой статус - Резервы правильно обновлялись на 'reserved' или 'released' - НО Stock.quantity_reserved не обновлялся - В результате товар показывался как полностью свободный, хотя был резерв Причина: - В сигнале rollback_sale_on_status_change использовался .update() - Это не вызывало сигнал update_stock_on_reservation_change - Stock не пересчитывался автоматически Решение: - Заменен .update() на .save(update_fields=[...]) в сигнале отката - Теперь при изменении резервов автоматически срабатывает сигнал - Stock корректно обновляется в обоих направлениях: * completed → резервы converted_to_sale → Stock обновляется * откат → резервы reserved/released → Stock обновляется - Убран костыль с ручным вызовом refresh_from_batches() Результат: - Элегантное единообразное решение для всех сценариев - Stock автоматически синхронизируется с резервами - Работает корректно при любых изменениях статуса заказа
This commit is contained in:
@@ -360,42 +360,24 @@ def rollback_sale_on_status_change(sender, instance, created, **kwargs):
|
||||
reservations_count = reservations.count()
|
||||
|
||||
if reservations_count > 0:
|
||||
# Используем update() вместо save() для массового обновления
|
||||
# Это предотвращает повторный вызов сигнала update_stock_on_reservation_change
|
||||
# и двойное обновление Stock
|
||||
update_fields = {'status': reservation_target_status}
|
||||
# Обновляем резервы через .save() чтобы сработал сигнал обновления Stock
|
||||
# Сигнал update_stock_on_reservation_change автоматически обновит Stock
|
||||
for reservation in reservations:
|
||||
reservation.status = reservation_target_status
|
||||
if reservation_target_status == 'released':
|
||||
reservation.released_at = timezone.now()
|
||||
# converted_at оставляем (для истории)
|
||||
|
||||
if reservation_target_status == 'released':
|
||||
update_fields['released_at'] = timezone.now()
|
||||
# converted_at оставляем (для истории)
|
||||
|
||||
reservations.update(**update_fields)
|
||||
# Используем save() с указанием измененных полей
|
||||
update_fields = ['status']
|
||||
if reservation_target_status == 'released':
|
||||
update_fields.append('released_at')
|
||||
reservation.save(update_fields=update_fields)
|
||||
|
||||
logger.info(
|
||||
f"✓ Обновлено {reservations_count} резервов: "
|
||||
f"converted_to_sale → {reservation_target_status}"
|
||||
)
|
||||
|
||||
# Обновляем Stock вручную, т.к. update() не вызывает сигналы
|
||||
# Группируем по product + warehouse для эффективности
|
||||
reservation_groups = reservations.values_list('product_id', 'warehouse_id').distinct()
|
||||
|
||||
for product_id, warehouse_id in reservation_groups:
|
||||
try:
|
||||
stock = Stock.objects.get(
|
||||
product_id=product_id,
|
||||
warehouse_id=warehouse_id
|
||||
)
|
||||
stock.refresh_from_batches()
|
||||
|
||||
logger.debug(
|
||||
f" Stock обновлен после изменения резервов: "
|
||||
f"product_id={product_id}, warehouse_id={warehouse_id}"
|
||||
)
|
||||
except Stock.DoesNotExist:
|
||||
logger.warning(
|
||||
f" Stock не найден для product_id={product_id}, warehouse_id={warehouse_id}"
|
||||
)
|
||||
else:
|
||||
logger.warning(
|
||||
f"⚠ Для заказа {instance.order_number} нет резервов в статусе 'converted_to_sale'"
|
||||
|
||||
Reference in New Issue
Block a user