Исправлена проблема с резервами при откате из статуса 'Выполнен'
Проблема: - При откате заказа из статуса '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()
|
reservations_count = reservations.count()
|
||||||
|
|
||||||
if reservations_count > 0:
|
if reservations_count > 0:
|
||||||
# Используем update() вместо save() для массового обновления
|
# Обновляем резервы через .save() чтобы сработал сигнал обновления Stock
|
||||||
# Это предотвращает повторный вызов сигнала update_stock_on_reservation_change
|
# Сигнал update_stock_on_reservation_change автоматически обновит Stock
|
||||||
# и двойное обновление Stock
|
for reservation in reservations:
|
||||||
update_fields = {'status': reservation_target_status}
|
reservation.status = reservation_target_status
|
||||||
|
|
||||||
if reservation_target_status == 'released':
|
if reservation_target_status == 'released':
|
||||||
update_fields['released_at'] = timezone.now()
|
reservation.released_at = timezone.now()
|
||||||
# converted_at оставляем (для истории)
|
# 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(
|
logger.info(
|
||||||
f"✓ Обновлено {reservations_count} резервов: "
|
f"✓ Обновлено {reservations_count} резервов: "
|
||||||
f"converted_to_sale → {reservation_target_status}"
|
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:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"⚠ Для заказа {instance.order_number} нет резервов в статусе 'converted_to_sale'"
|
f"⚠ Для заказа {instance.order_number} нет резервов в статусе 'converted_to_sale'"
|
||||||
|
|||||||
Reference in New Issue
Block a user