Исправлена критическая проблема с резервами при смене статуса заказа
Проблема: - При смене статуса заказа на 'Выполнен' товар списывался со склада - Резервы обновлялись на статус 'converted_to_sale' - НО Stock.quantity_reserved не обновлялся автоматически - В результате резервы продолжали 'держать' товар, хотя он уже продан Решение: 1. Изменен сигнал create_sale_on_order_completion: - Используется .save(update_fields=[...]) вместо .update() - Это вызывает сигнал update_stock_on_reservation_change - Убран костыль с ручным вызовом refresh_from_batches() 2. Оптимизирован сигнал update_stock_on_reservation_change: - Stock обновляется ТОЛЬКО при изменении status или quantity - При изменении других полей (даты и т.д.) Stock НЕ пересчитывается - Предотвращены лишние пересчёты и улучшена производительность 3. Добавлены диагностические инструменты: - check_stock_103.py - для диагностики проблем с Stock - fix_stock_after_sale.py - команда для исправления старых заказов - diagnose_reservation_issue.py - универсальная диагностика Результат: - Элегантное решение без дублирования логики - Stock автоматически обновляется при изменении резервов - Работает везде, не только в заказах - Оптимизировано для производительности
This commit is contained in:
117
myproject/inventory/management/commands/fix_stock_after_sale.py
Normal file
117
myproject/inventory/management/commands/fix_stock_after_sale.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Команда для исправления Stock после обработки заказов.
|
||||
Пересчитывает quantity_reserved для всех товаров.
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from inventory.models import Stock, Reservation
|
||||
from django.db.models import Sum
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Исправляет quantity_reserved в Stock после обработки заказов'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--order',
|
||||
type=int,
|
||||
help='Номер заказа для исправления (опционально)'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
order_number = options.get('order')
|
||||
|
||||
if order_number:
|
||||
self.stdout.write(f"Исправление Stock для заказа {order_number}...")
|
||||
self.fix_for_order(order_number)
|
||||
else:
|
||||
self.stdout.write("Исправление всех Stock...")
|
||||
self.fix_all_stock()
|
||||
|
||||
def fix_for_order(self, order_number):
|
||||
"""Исправить Stock для конкретного заказа"""
|
||||
from orders.models import Order
|
||||
|
||||
try:
|
||||
order = Order.objects.get(order_number=order_number)
|
||||
except Order.DoesNotExist:
|
||||
self.stdout.write(
|
||||
self.style.ERROR(f"Заказ {order_number} не найден!")
|
||||
)
|
||||
return
|
||||
|
||||
# Получаем все резервы для этого заказа
|
||||
reservations = Reservation.objects.filter(
|
||||
order_item__order=order
|
||||
).values('product_id', 'warehouse_id').distinct()
|
||||
|
||||
fixed_count = 0
|
||||
for res in reservations:
|
||||
product_id = res['product_id']
|
||||
warehouse_id = res['warehouse_id']
|
||||
|
||||
try:
|
||||
stock = Stock.objects.get(
|
||||
product_id=product_id,
|
||||
warehouse_id=warehouse_id
|
||||
)
|
||||
|
||||
old_reserved = stock.quantity_reserved
|
||||
stock.refresh_from_batches()
|
||||
new_reserved = stock.quantity_reserved
|
||||
|
||||
if old_reserved != new_reserved:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"✓ Обновлено: product_id={product_id}, "
|
||||
f"warehouse_id={warehouse_id}, "
|
||||
f"reserved: {old_reserved} → {new_reserved}"
|
||||
)
|
||||
)
|
||||
fixed_count += 1
|
||||
else:
|
||||
self.stdout.write(
|
||||
f" Без изменений: product_id={product_id}"
|
||||
)
|
||||
|
||||
except Stock.DoesNotExist:
|
||||
self.stdout.write(
|
||||
self.style.WARNING(
|
||||
f"⚠ Stock не найден: product_id={product_id}, "
|
||||
f"warehouse_id={warehouse_id}"
|
||||
)
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"\n✅ Готово! Обновлено {fixed_count} Stock для заказа {order_number}"
|
||||
)
|
||||
)
|
||||
|
||||
def fix_all_stock(self):
|
||||
"""Пересчитать все Stock"""
|
||||
stocks = Stock.objects.all()
|
||||
total = stocks.count()
|
||||
fixed_count = 0
|
||||
|
||||
self.stdout.write(f"Найдено {total} Stock записей...")
|
||||
|
||||
for stock in stocks:
|
||||
old_reserved = stock.quantity_reserved
|
||||
stock.refresh_from_batches()
|
||||
new_reserved = stock.quantity_reserved
|
||||
|
||||
if old_reserved != new_reserved:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"✓ {stock.product.name} на {stock.warehouse.name}: "
|
||||
f"reserved {old_reserved} → {new_reserved}"
|
||||
)
|
||||
)
|
||||
fixed_count += 1
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"\n✅ Готово! Обновлено {fixed_count} из {total} Stock"
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user