- Updated generated timestamps in initial migrations of accounts, customers, inventory, orders, products, tenants, and user_roles apps - Reflect new generation time from 08:35 to 23:23 on 2026-01-03 - No changes to migration logic or schema detected - Ensures migration files align with most recent generation time for consistency
179 lines
8.6 KiB
Python
179 lines
8.6 KiB
Python
"""
|
||
Management команда для отмены всех резервов в указанной схеме
|
||
Используется для очистки "зависших" резервов после ошибок
|
||
"""
|
||
from django.core.management.base import BaseCommand
|
||
from django.db import connection, transaction
|
||
from django.utils import timezone
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = 'Отменяет все резервы (меняет статус на released) в указанной схеме'
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument(
|
||
'--schema',
|
||
type=str,
|
||
required=True,
|
||
help='Схема базы данных (tenant) для работы'
|
||
)
|
||
parser.add_argument(
|
||
'--confirm',
|
||
action='store_true',
|
||
help='Подтверждение операции (без этого флага будет только показан список резервов)'
|
||
)
|
||
|
||
def handle(self, *args, **options):
|
||
schema_name = options['schema']
|
||
confirm = options.get('confirm', False)
|
||
|
||
self.stdout.write('=' * 60)
|
||
self.stdout.write(f'[INFO] Схема: {schema_name}')
|
||
self.stdout.write('=' * 60)
|
||
|
||
with connection.cursor() as cursor:
|
||
# Устанавливаем схему
|
||
cursor.execute(f'SET search_path TO {schema_name}')
|
||
|
||
# Проверяем наличие резервов со статусом 'reserved'
|
||
cursor.execute(f"""
|
||
SELECT
|
||
r.id,
|
||
p.name as product_name,
|
||
r.quantity,
|
||
w.name as warehouse_name,
|
||
r.reserved_at,
|
||
r.order_item_id,
|
||
r.showcase_id,
|
||
r.product_kit_id
|
||
FROM {schema_name}.inventory_reservation r
|
||
JOIN {schema_name}.products_product p ON p.id = r.product_id
|
||
JOIN {schema_name}.inventory_warehouse w ON w.id = r.warehouse_id
|
||
WHERE r.status = 'reserved'
|
||
ORDER BY r.reserved_at
|
||
""")
|
||
|
||
reservations = cursor.fetchall()
|
||
|
||
if not reservations:
|
||
self.stdout.write(self.style.SUCCESS('[OK] В схеме нет активных резервов со статусом "reserved"'))
|
||
return
|
||
|
||
self.stdout.write(f'\n[INFO] Найдено резервов со статусом "reserved": {len(reservations)}\n')
|
||
|
||
# Группируем резервы по типу
|
||
order_reservations = []
|
||
showcase_reservations = []
|
||
kit_reservations = []
|
||
other_reservations = []
|
||
|
||
for res in reservations:
|
||
res_id, product_name, quantity, warehouse_name, reserved_at, order_item_id, showcase_id, product_kit_id = res
|
||
|
||
if order_item_id:
|
||
order_reservations.append(res)
|
||
elif showcase_id:
|
||
showcase_reservations.append(res)
|
||
elif product_kit_id:
|
||
kit_reservations.append(res)
|
||
else:
|
||
other_reservations.append(res)
|
||
|
||
# Выводим статистику
|
||
if order_reservations:
|
||
self.stdout.write(f' - Резервы для заказов: {len(order_reservations)}')
|
||
if showcase_reservations:
|
||
self.stdout.write(f' - Резервы для витрин: {len(showcase_reservations)}')
|
||
if kit_reservations:
|
||
self.stdout.write(f' - Резервы для комплектов: {len(kit_reservations)}')
|
||
if other_reservations:
|
||
self.stdout.write(f' - Прочие резервы: {len(other_reservations)}')
|
||
|
||
# Выводим детали первых 10 резервов
|
||
self.stdout.write('\n[INFO] Первые 10 резервов:')
|
||
for res in reservations[:10]:
|
||
res_id, product_name, quantity, warehouse_name, reserved_at, order_item_id, showcase_id, product_kit_id = res
|
||
|
||
context = []
|
||
if order_item_id:
|
||
context.append(f'заказ #{order_item_id}')
|
||
if showcase_id:
|
||
context.append(f'витрина #{showcase_id}')
|
||
if product_kit_id:
|
||
context.append(f'комплект #{product_kit_id}')
|
||
context_str = ', '.join(context) if context else 'без привязки'
|
||
|
||
self.stdout.write(
|
||
f' #{res_id}: {product_name} - {quantity} ед. на складе "{warehouse_name}" '
|
||
f'({reserved_at.strftime("%Y-%m-%d %H:%M:%S")}, {context_str})'
|
||
)
|
||
|
||
if not confirm:
|
||
self.stdout.write('\n' + '=' * 60)
|
||
self.stdout.write(self.style.WARNING('[ВНИМАНИЕ] Это режим предварительного просмотра'))
|
||
self.stdout.write(self.style.WARNING(f'Для отмены всех {len(reservations)} резервов запустите команду с флагом --confirm:'))
|
||
self.stdout.write(f' python manage.py clear_reservations --schema={schema_name} --confirm')
|
||
self.stdout.write('=' * 60)
|
||
return
|
||
|
||
# Подтверждение получено - отменяем все резервы
|
||
self.stdout.write('\n' + '=' * 60)
|
||
self.stdout.write(self.style.WARNING(f'[НАЧАЛО] Отмена {len(reservations)} резервов...'))
|
||
self.stdout.write('=' * 60)
|
||
|
||
with transaction.atomic():
|
||
released_at = timezone.now()
|
||
|
||
# Обновляем все резервы одним запросом
|
||
cursor.execute(f"""
|
||
UPDATE {schema_name}.inventory_reservation
|
||
SET status = 'released',
|
||
released_at = %s
|
||
WHERE status = 'reserved'
|
||
""", [released_at])
|
||
|
||
updated_count = cursor.rowcount
|
||
|
||
self.stdout.write(f'\n[OK] Обновлено резервов: {updated_count}')
|
||
|
||
# Теперь пересчитываем quantity_reserved для всех затронутых Stock записей
|
||
self.stdout.write('\n[INFO] Пересчет quantity_reserved для Stock записей...')
|
||
|
||
# Получаем уникальные комбинации product_id + warehouse_id из освобожденных резервов
|
||
cursor.execute(f"""
|
||
SELECT DISTINCT product_id, warehouse_id
|
||
FROM {schema_name}.inventory_reservation
|
||
WHERE released_at = %s
|
||
""", [released_at])
|
||
|
||
stock_updates = cursor.fetchall()
|
||
|
||
for product_id, warehouse_id in stock_updates:
|
||
# Пересчитываем quantity_reserved для этой комбинации
|
||
cursor.execute(f"""
|
||
SELECT COALESCE(SUM(quantity), 0)
|
||
FROM {schema_name}.inventory_reservation
|
||
WHERE product_id = %s
|
||
AND warehouse_id = %s
|
||
AND status = 'reserved'
|
||
""", [product_id, warehouse_id])
|
||
|
||
total_reserved = cursor.fetchone()[0]
|
||
|
||
# Обновляем Stock
|
||
cursor.execute(f"""
|
||
UPDATE {schema_name}.inventory_stock
|
||
SET quantity_reserved = %s,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE product_id = %s
|
||
AND warehouse_id = %s
|
||
""", [total_reserved, product_id, warehouse_id])
|
||
|
||
self.stdout.write(f'[OK] Обновлено Stock записей: {len(stock_updates)}')
|
||
|
||
self.stdout.write('\n' + '=' * 60)
|
||
self.stdout.write(self.style.SUCCESS('[ЗАВЕРШЕНО] Все резервы успешно отменены!'))
|
||
self.stdout.write(f' Освобождено резервов: {updated_count}')
|
||
self.stdout.write(f' Обновлено Stock записей: {len(stock_updates)}')
|
||
self.stdout.write('=' * 60)
|