""" Скрипт для очистки дубликатов резервов в базе данных (для tenant: buba). Проблема: У некоторых позиций заказов (OrderItem) существует несколько резервов в статусе 'reserved', что вызывает ошибку MultipleObjectsReturned. Решение: Оставляем только первый резерв, остальные удаляем. """ import os import django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') django.setup() from django.db import transaction, connection from django.db.models import Count from inventory.models import Reservation from orders.models import OrderItem # Устанавливаем tenant-схему def set_tenant_schema(schema_name='buba'): """Переключаемся на нужную tenant-схему""" connection.set_schema(schema_name) print(f"✓ Переключились на схему: {schema_name}") def find_duplicate_reservations(): """Находит OrderItem с несколькими резервами в статусе 'reserved'""" print("\n" + "="*80) print("Поиск дубликатов резервов...") print("="*80) # Группируем резервы по order_item и считаем количество duplicates = Reservation.objects.filter( status='reserved' ).values('order_item').annotate( count=Count('id') ).filter(count__gt=1).order_by('-count') print(f"\nНайдено OrderItem с дубликатами: {duplicates.count()}") if duplicates.count() == 0: print("✅ Дубликатов не обнаружено!") return [] # Выводим детали problem_items = [] for dup in duplicates: order_item_id = dup['order_item'] count = dup['count'] try: order_item = OrderItem.objects.get(id=order_item_id) product_name = ( order_item.product.sku if order_item.product else order_item.product_kit.name if order_item.product_kit else "Unknown" ) print(f"\n OrderItem #{order_item_id}:") print(f" Заказ: #{order_item.order.order_number}") print(f" Товар: {product_name}") print(f" Количество резервов: {count}") # Показываем все резервы reservations = Reservation.objects.filter( order_item=order_item, status='reserved' ).order_by('reserved_at') # Сортируем по дате создания for idx, res in enumerate(reservations, 1): marker = "✓ ОСТАВИТЬ" if idx == 1 else "✗ УДАЛИТЬ" print(f" {marker} - Резерв #{res.id}: qty={res.quantity}, создан {res.reserved_at}") problem_items.append({ 'order_item': order_item, 'count': count, 'reservations': list(reservations) }) except OrderItem.DoesNotExist: print(f"\n ⚠ OrderItem #{order_item_id} не существует (удален)") return problem_items def clean_duplicate_reservations(problem_items, dry_run=True): """ Очищает дубликаты резервов. Args: problem_items: Список OrderItem с дубликатами dry_run: Если True, только показывает что будет сделано, но не выполняет """ print("\n" + "="*80) if dry_run: print("РЕЖИМ ПРОВЕРКИ (dry_run=True) - изменения НЕ будут сохранены") else: print("⚠ РЕЖИМ ОЧИСТКИ (dry_run=False) - изменения БУДУТ сохранены!") print("="*80) if not problem_items: print("\nНечего очищать!") return total_deleted = 0 for item_data in problem_items: order_item = item_data['order_item'] reservations = item_data['reservations'] # Оставляем первый (самый старый) резерв first_reservation = reservations[0] duplicates = reservations[1:] print(f"\nOrderItem #{order_item.id} (Заказ #{order_item.order.order_number}):") print(f" Оставляем: Резерв #{first_reservation.id}") print(f" Удаляем: {len(duplicates)} дубликатов") if not dry_run: with transaction.atomic(): for dup in duplicates: print(f" ✗ Удаляем Резерв #{dup.id}") dup.delete() total_deleted += 1 else: for dup in duplicates: print(f" [DRY RUN] Будет удален Резерв #{dup.id}") total_deleted += len(duplicates) print("\n" + "="*80) if dry_run: print(f"[DRY RUN] Будет удалено резервов: {total_deleted}") else: print(f"✅ УДАЛЕНО резервов: {total_deleted}") print("="*80) def main(): """Главная функция""" print("\n" + "="*80) print("ОЧИСТКА ДУБЛИКАТОВ РЕЗЕРВОВ") print("="*80) # Переключаемся на tenant buba set_tenant_schema('buba') # Шаг 1: Находим дубликаты problem_items = find_duplicate_reservations() if not problem_items: return # Шаг 2: Сначала делаем dry run print("\n" + "="*80) print("ШАГ 1: ПРОВЕРКА (без изменений)") print("="*80) clean_duplicate_reservations(problem_items, dry_run=True) # Шаг 3: Спрашиваем подтверждение print("\n" + "="*80) response = input("\n⚠ Выполнить очистку? (yes/no): ") if response.lower() in ['yes', 'y', 'да', 'д']: print("\n" + "="*80) print("ШАГ 2: ОЧИСТКА (с изменениями)") print("="*80) clean_duplicate_reservations(problem_items, dry_run=False) # Проверяем что дубликатов больше нет print("\n" + "="*80) print("ПРОВЕРКА ПОСЛЕ ОЧИСТКИ") print("="*80) remaining = find_duplicate_reservations() if not remaining: print("\n✅ ВСЕ ДУБЛИКАТЫ УСПЕШНО УДАЛЕНЫ!") else: print(f"\n⚠ Еще остались дубликаты: {len(remaining)}") else: print("\n❌ Очистка отменена") if __name__ == '__main__': main()