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
This commit is contained in:
2026-01-04 02:29:49 +03:00
parent bcda94f09a
commit 123f330a26
11 changed files with 188 additions and 10 deletions

View File

@@ -0,0 +1,178 @@
"""
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)