Files
octopus/myproject/diagnose_reservation_issue.py
Andrey Smakotin e4cb175db2 Исправлена критическая проблема с резервами при смене статуса заказа
Проблема:
- При смене статуса заказа на 'Выполнен' товар списывался со склада
- Резервы обновлялись на статус '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 автоматически обновляется при изменении резервов
- Работает везде, не только в заказах
- Оптимизировано для производительности
2025-12-01 02:34:54 +03:00

172 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
"""
Скрипт для диагностики проблемы с резервами при смене статуса на 'completed'.
Проверяет:
1. Изменяется ли статус резервов на 'converted_to_sale'
2. Создаются ли Sale
3. Списывается ли товар со склада
4. Освобождаются ли резервы
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
from django.db import connection
from orders.models import Order, OrderStatus
from inventory.models import Reservation, Sale, Stock
def diagnose_order(order_number):
"""Диагностика конкретного заказа"""
print(f"\n{'='*80}")
print(f"ДИАГНОСТИКА ЗАКАЗА #{order_number}")
print(f"{'='*80}\n")
# Проверяем тенант
print(f"📌 Текущая схема БД: {connection.schema_name}")
try:
order = Order.objects.get(order_number=order_number)
except Order.DoesNotExist:
print(f"❌ ОШИБКА: Заказ #{order_number} не найден!")
return
print(f"✓ Заказ найден")
print(f" - Клиент: {order.customer.name}")
print(f" - Статус: {order.status.name if order.status else 'Нет статуса'} (code: {order.status.code if order.status else 'None'})")
print(f" - Дата создания: {order.created_at}")
print(f" - Дата обновления: {order.updated_at}")
# Проверяем позиции заказа
print(f"\n📦 ПОЗИЦИИ ЗАКАЗА:")
items = order.items.all()
if not items.exists():
print(" ⚠ Нет позиций в заказе")
else:
for item in items:
product = item.product or item.product_kit
print(f" - {product.name}: {item.quantity} шт. по {item.price} руб.")
# Проверяем резервы
print(f"\n📝 РЕЗЕРВЫ:")
reservations = Reservation.objects.filter(order_item__order=order)
if not reservations.exists():
print("НЕТ РЕЗЕРВОВ для этого заказа!")
else:
for res in reservations:
print(f" - {res.product.name}:")
print(f" • Количество: {res.quantity}")
print(f" • Статус: {res.get_status_display()} ({res.status})")
print(f" • Склад: {res.warehouse.name}")
print(f" • Зарезервировано: {res.reserved_at}")
if res.converted_at:
print(f" • Конвертировано: {res.converted_at}")
if res.released_at:
print(f" • Освобождено: {res.released_at}")
# Проверяем Sale
print(f"\n💰 ПРОДАЖИ (Sale):")
sales = Sale.objects.filter(order=order)
if not sales.exists():
print("НЕТ ПРОДАЖ для этого заказа!")
if order.status and order.status.code == 'completed':
print(" ❌ ПРОБЛЕМА: Заказ в статусе 'completed', но Sale не созданы!")
else:
for sale in sales:
print(f" - {sale.product.name}:")
print(f" • Количество: {sale.quantity}")
print(f" • Цена продажи: {sale.sale_price}")
print(f" • Склад: {sale.warehouse.name}")
print(f" • Дата: {sale.created_at}")
# Проверяем распределение по партиям
from inventory.models import SaleBatchAllocation
allocations = SaleBatchAllocation.objects.filter(sale=sale)
if allocations.exists():
print(f" • Распределение по партиям:")
for alloc in allocations:
print(f" - Партия #{alloc.batch.id}: {alloc.quantity} шт.")
# Проверяем Stock
print(f"\n📊 ОСТАТКИ НА СКЛАДЕ (Stock):")
for item in items:
product = item.product or item.product_kit
warehouse = order.pickup_warehouse or order.items.first().order.pickup_warehouse
if warehouse:
try:
stock = Stock.objects.get(product=product, warehouse=warehouse)
print(f" - {product.name} на складе {warehouse.name}:")
print(f"Всего: {stock.quantity}")
print(f" • Зарезервировано: {stock.reserved_quantity}")
print(f" • Доступно: {stock.available_quantity}")
except Stock.DoesNotExist:
print(f" - {product.name}: ❌ Stock не найден")
else:
print(f" - {product.name}: ⚠ Склад не определён")
# Проверяем историю изменений
print(f"\n📜 ИСТОРИЯ ИЗМЕНЕНИЙ СТАТУСА:")
history = order.history.all()[:5] # Последние 5 записей
for idx, record in enumerate(history, 1):
status_name = record.status.name if record.status else "Нет статуса"
print(f" {idx}. {status_name} - {record.history_date}")
# Выводим выводы
print(f"\n{'='*80}")
print("ДИАГНОСТИКА:")
print(f"{'='*80}")
issues = []
# Проверка 1: Резервы существуют?
if not reservations.exists():
issues.append("❌ КРИТИЧНО: Нет резервов для заказа")
# Проверка 2: Статус 'completed' + нет Sale
if order.status and order.status.code == 'completed':
if not sales.exists():
issues.append("❌ КРИТИЧНО: Заказ 'completed', но Sale не созданы")
# Проверка 3: Статус резервов
reserved_count = reservations.filter(status='reserved').count()
converted_count = reservations.filter(status='converted_to_sale').count()
if reserved_count > 0:
issues.append(f"❌ ПРОБЛЕМА: {reserved_count} резервов ещё в статусе 'reserved' при completed заказе")
if converted_count == 0 and reservations.exists():
issues.append("❌ ПРОБЛЕМА: Ни один резерв не конвертирован в продажу")
if issues:
print("\n🔴 НАЙДЕНЫ ПРОБЛЕМЫ:")
for issue in issues:
print(f" {issue}")
else:
print("\n✅ Проблем не обнаружено")
print(f"\n{'='*80}\n")
def main():
print("\n" + "="*80)
print("ДИАГНОСТИКА ПРОБЛЕМЫ С РЕЗЕРВАМИ")
print("="*80)
# Спрашиваем номер заказа
order_number_input = input("\nВведите номер заказа для диагностики: ").strip()
try:
order_number = int(order_number_input)
except ValueError:
print("❌ Ошибка: Введите корректный номер заказа (число)")
return
diagnose_order(order_number)
if __name__ == "__main__":
main()