Fix product reservation system for demo orders

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>
This commit is contained in:
2025-11-08 00:04:55 +03:00
parent e3bab2252e
commit fcc7f2263d
5 changed files with 291 additions and 5 deletions

View File

@@ -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✓ Резервы успешно восстановлены!'))