diff --git a/myproject/check_orders.py b/myproject/check_orders.py new file mode 100644 index 0000000..ffc593e --- /dev/null +++ b/myproject/check_orders.py @@ -0,0 +1,53 @@ +""" +Проверка созданных заказов и резервов +""" +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') +django.setup() + +from django.db import connection + +with connection.cursor() as cursor: + cursor.execute("SET search_path TO grach") + + # Считаем заказы + cursor.execute("SELECT COUNT(*) FROM grach.orders_order") + orders_count = cursor.fetchone()[0] + print(f"Заказов: {orders_count}") + + # Считаем позиции заказов + cursor.execute("SELECT COUNT(*) FROM grach.orders_orderitem") + items_count = cursor.fetchone()[0] + print(f"Позиций в заказах: {items_count}") + + # Считаем резервы + cursor.execute("SELECT COUNT(*) FROM grach.inventory_reservation") + reservations_count = cursor.fetchone()[0] + print(f"Резервов: {reservations_count}") + + # Детали по заказам без резервов + print("\nПервые 10 позиций без резервов:") + cursor.execute(""" + SELECT + o.order_number, + oi.id as item_id, + p.name as product_name, + oi.quantity, + COUNT(r.id) as reservations_count + FROM grach.orders_order o + JOIN grach.orders_orderitem oi ON oi.order_id = o.id + LEFT JOIN grach.products_product p ON p.id = oi.product_id + LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id + GROUP BY o.order_number, oi.id, p.name, oi.quantity + HAVING COUNT(r.id) = 0 + ORDER BY o.order_number + LIMIT 10 + """) + rows = cursor.fetchall() + if rows: + for row in rows: + print(f" Заказ {row[0]}: ItemID={row[1]}, Товар=\"{row[2]}\", Кол-во={row[3]}, Резервов={row[4]}") + else: + print(" Все позиции имеют резервы!") diff --git a/myproject/fix_reservations.sql b/myproject/fix_reservations.sql new file mode 100644 index 0000000..ddd1daf --- /dev/null +++ b/myproject/fix_reservations.sql @@ -0,0 +1,59 @@ +-- Создание резервов для всех позиций заказов без резервов +SET search_path TO grach; + +-- Проверяем наличие активного склада +DO $$ +DECLARE + warehouse_id_val INT; + created_count INT := 0; +BEGIN + -- Получаем ID активного склада (если есть) + SELECT id INTO warehouse_id_val + FROM grach.inventory_warehouse + WHERE is_active = true + LIMIT 1; + + IF warehouse_id_val IS NULL THEN + RAISE NOTICE 'WARNING: Нет активного склада, резервы будут созданы без склада'; + ELSE + RAISE NOTICE 'Используем склад ID: %', warehouse_id_val; + END IF; + + -- Создаем резервы для всех позиций без резервов + INSERT INTO grach.inventory_reservation ( + order_item_id, + product_id, + warehouse_id, + quantity, + status, + reserved_at, + released_at, + converted_at + ) + SELECT + oi.id, + COALESCE(oi.product_id, oi.product_kit_id), + warehouse_id_val, + oi.quantity, + 'reserved', + CURRENT_TIMESTAMP, + NULL, + NULL + FROM grach.orders_orderitem oi + LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id + WHERE r.id IS NULL -- Только позиции без резервов + AND (oi.product_id IS NOT NULL OR oi.product_kit_id IS NOT NULL) -- Есть товар + ON CONFLICT DO NOTHING; + + GET DIAGNOSTICS created_count = ROW_COUNT; + + RAISE NOTICE 'Создано резервов: %', created_count; +END $$; + +-- Проверяем результат +SELECT + COUNT(*) as total_items, + COUNT(r.id) as items_with_reservations, + COUNT(*) - COUNT(r.id) as items_without_reservations +FROM grach.orders_orderitem oi +LEFT JOIN grach.inventory_reservation r ON r.order_item_id = oi.id; diff --git a/myproject/orders/management/commands/create_demo_orders.py b/myproject/orders/management/commands/create_demo_orders.py index 18feee3..277173e 100644 --- a/myproject/orders/management/commands/create_demo_orders.py +++ b/myproject/orders/management/commands/create_demo_orders.py @@ -1,5 +1,7 @@ """ Management команда для создания демо-заказов на разные даты +ВАЖНО: Создает заказы через Django ORM, что автоматически активирует +сигналы резервирования товаров! """ from django.core.management.base import BaseCommand from django.utils import timezone @@ -15,7 +17,7 @@ from products.models import Product class Command(BaseCommand): - help = 'Создает 20-30 демо-заказов на разные даты' + help = 'Создает демо-заказы через ORM (с автоматическим резервированием товаров)' def add_arguments(self, parser): parser.add_argument( @@ -39,7 +41,8 @@ class Command(BaseCommand): with connection.cursor() as cursor: cursor.execute(f'SET search_path TO {schema_name}') - self.stdout.write(f'Начинаем создание демо-заказов в схеме {schema_name}...') + self.stdout.write(f'[НАЧАЛО] Создание {count} демо-заказов в схеме {schema_name}...') + self.stdout.write('[INFO] Заказы создаются через ORM - резервы товаров будут созданы автоматически!') # Проверяем наличие необходимых данных customers = list(Customer.objects.all()) @@ -193,10 +196,11 @@ class Command(BaseCommand): order.save() created_count += 1 - self.stdout.write(f' Создан заказ #{order.order_number} на {delivery_date}') + self.stdout.write(f' [OK] Заказ #{order.order_number} на {delivery_date} (товаров: {len(order_products)})') except Exception as e: - self.stdout.write(self.style.ERROR(f'Ошибка при создании заказа {i+1}: {str(e)}')) + self.stdout.write(self.style.ERROR(f'[ОШИБКА] Заказ {i+1}: {str(e)}')) - self.stdout.write(self.style.SUCCESS(f'\nУспешно создано {created_count} заказов!')) + self.stdout.write(self.style.SUCCESS(f'\n[ЗАВЕРШЕНО] Успешно создано {created_count} заказов!')) self.stdout.write(f'Даты доставки: от {today - timedelta(days=15)} до {today + timedelta(days=15)}') + self.stdout.write(self.style.SUCCESS('\n[ВАЖНО] Резервы товаров созданы автоматически через Django сигналы!')) diff --git a/myproject/orders/management/commands/fix_missing_reservations.py b/myproject/orders/management/commands/fix_missing_reservations.py new file mode 100644 index 0000000..d1430e4 --- /dev/null +++ b/myproject/orders/management/commands/fix_missing_reservations.py @@ -0,0 +1,138 @@ +""" +Management команда для восстановления недостающих резервов товаров +Используется для исправления заказов, созданных напрямую через SQL +""" +from django.core.management.base import BaseCommand +from django.db import connection, transaction +from datetime import datetime + +from orders.models import OrderItem +from inventory.models import Reservation, Warehouse + + +class Command(BaseCommand): + help = 'Создает недостающие резервы для существующих заказов' + + def add_arguments(self, parser): + parser.add_argument( + '--schema', + type=str, + default='grach', + help='Схема базы данных (tenant) для работы' + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Показать что будет сделано, но не применять изменения' + ) + + def handle(self, *args, **options): + schema_name = options['schema'] + dry_run = options['dry_run'] + + # Устанавливаем схему для работы с tenant + with connection.cursor() as cursor: + cursor.execute(f'SET search_path TO {schema_name}') + + self.stdout.write(f'[НАЧАЛО] Поиск заказов без резервов в схеме {schema_name}...') + + if dry_run: + self.stdout.write(self.style.WARNING('[DRY RUN] Режим просмотра - изменения НЕ будут применены')) + + # Получаем активный склад (если есть) + try: + warehouse = Warehouse.objects.filter(is_active=True).first() + if warehouse: + self.stdout.write(f'[INFO] Используем склад: {warehouse.name}') + else: + self.stdout.write(self.style.WARNING('[ВНИМАНИЕ] Нет активного склада, резервы будут созданы без привязки к складу')) + except Exception as e: + self.stdout.write(self.style.WARNING(f'[ВНИМАНИЕ] Не удалось получить склад: {e}')) + warehouse = None + + # Находим все OrderItem без резервов + items_without_reservations = OrderItem.objects.filter( + reservations__isnull=True + ).select_related('order', 'product', 'product_kit') + + total_items = items_without_reservations.count() + + if total_items == 0: + self.stdout.write(self.style.SUCCESS('[OK] Все заказы имеют резервы!')) + return + + self.stdout.write(f'[НАЙДЕНО] {total_items} позиций заказов без резервов') + + created_count = 0 + errors_count = 0 + + with transaction.atomic(): + for item in items_without_reservations: + try: + # Определяем товар (может быть product или product_kit) + product = item.product if item.product else item.product_kit + + if not product: + self.stdout.write( + self.style.WARNING( + f'[ПРОПУСК] OrderItem #{item.id}: нет товара' + ) + ) + errors_count += 1 + continue + + # Создаем резерв + if not dry_run: + reservation = Reservation.objects.create( + order_item=item, + product=product, + warehouse=warehouse, + quantity=item.quantity, + status='reserved' + ) + + self.stdout.write( + f' [OK] Резерв #{reservation.id}: ' + f'Заказ {item.order.order_number}, ' + f'Товар "{product.name}", ' + f'Кол-во: {item.quantity}' + ) + else: + self.stdout.write( + f' [DRY RUN] Будет создан резерв: ' + f'Заказ {item.order.order_number}, ' + f'Товар "{product.name}", ' + f'Кол-во: {item.quantity}' + ) + + created_count += 1 + + except Exception as e: + self.stdout.write( + self.style.ERROR( + f'[ОШИБКА] OrderItem #{item.id}: {str(e)}' + ) + ) + errors_count += 1 + + if dry_run: + # В режиме dry-run откатываем транзакцию + transaction.set_rollback(True) + self.stdout.write( + self.style.WARNING( + f'\n[DRY RUN] Изменения НЕ применены (транзакция откачена)' + ) + ) + + # Итоги + self.stdout.write('\n' + '=' * 60) + if dry_run: + self.stdout.write(self.style.WARNING('[DRY RUN] Результаты (БЕЗ изменений):')) + self.stdout.write(f' Будет создано резервов: {created_count}') + self.stdout.write(f' Ошибок/пропусков: {errors_count}') + self.stdout.write('\nДля применения изменений запустите без флага --dry-run') + else: + self.stdout.write(self.style.SUCCESS('[ЗАВЕРШЕНО] Результаты:')) + self.stdout.write(f' Создано резервов: {created_count}') + self.stdout.write(f' Ошибок/пропусков: {errors_count}') + self.stdout.write(self.style.SUCCESS('\n✓ Резервы успешно восстановлены!')) diff --git a/myproject/run_fix_reservations.py b/myproject/run_fix_reservations.py new file mode 100644 index 0000000..b444090 --- /dev/null +++ b/myproject/run_fix_reservations.py @@ -0,0 +1,32 @@ +""" +Скрипт для создания резервов для существующих заказов +""" +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') +django.setup() + +from django.db import connection + +# Читаем SQL скрипт +with open('fix_reservations.sql', 'r', encoding='utf-8') as f: + sql = f.read() + +# Выполняем SQL +with connection.cursor() as cursor: + try: + cursor.execute(sql) + print("[OK] SQL script executed successfully!") + print("\nResults:") + # Получаем результат последнего SELECT + rows = cursor.fetchall() + if rows: + row = rows[0] + print(f" Total items: {row[0]}") + print(f" Items with reservations: {row[1]}") + print(f" Items without reservations: {row[2]}") + print("\n[OK] Reservations created!") + except Exception as e: + print(f"[ERROR] {e}") + raise