From a5a983b198e024550e243309ef88c8a0c610865b Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 1 Dec 2025 02:40:40 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B0=20=D1=81=20=D1=80=D0=B5=D0=B7=D0=B5=D1=80=D0=B2=D0=B0?= =?UTF-8?q?=D0=BC=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=20=D0=B8=D0=B7=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=D0=B0=20'=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: - При откате заказа из статуса '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 автоматически синхронизируется с резервами - Работает корректно при любых изменениях статуса заказа --- myproject/inventory/signals.py | 44 ++++++++++------------------------ 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index 1e1b94a..ab006a9 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -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} - - if reservation_target_status == 'released': - update_fields['released_at'] = timezone.now() - # converted_at оставляем (для истории) - - reservations.update(**update_fields) + # Обновляем резервы через .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 оставляем (для истории) + + # Используем 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'"