Проблема: - При смене статуса заказа на 'Выполнен' товар списывался со склада - Резервы обновлялись на статус '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 автоматически обновляется при изменении резервов - Работает везде, не только в заказах - Оптимизировано для производительности
172 lines
7.3 KiB
Python
172 lines
7.3 KiB
Python
#!/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()
|