PROBLEM ANALYSIS: - SQL script created orders bypassing Django ORM - Django signals (post_save) didn't trigger - No reservations were created automatically - Found 51 orders with 102 items and 0 reservations SOLUTION IMPLEMENTED: 1. Updated create_demo_orders command: - Added clear documentation about ORM usage - Already uses ORM (.save()) which triggers signals - Added informative messages about automatic reservations 2. Created fix_missing_reservations command: - Finds OrderItems without reservations - Creates missing Reservation records - Supports --dry-run mode for safety - Handles missing warehouses gracefully 3. Created SQL fix script: - Direct SQL approach for existing data - Creates reservations for all 102 items - Status: 'reserved' - Verified: All items now have reservations 4. Added verification scripts: - check_orders.py: Shows orders/items/reservations count - run_fix_reservations.py: Executes SQL fix RESULTS: - ✓ 102 reservations created for existing orders - ✓ Future orders will use ORM and create reservations automatically - ✓ System now works correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
139 lines
6.2 KiB
Python
139 lines
6.2 KiB
Python
"""
|
||
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✓ Резервы успешно восстановлены!'))
|