Files
octopus/myproject/inventory/management/commands/clear_reservations.py
Andrey Smakotin 123f330a26 chore(migrations): update migration generation timestamps to latest time
- 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
2026-01-04 02:29:49 +03:00

179 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)