# -*- coding: utf-8 -*- from celery import shared_task from django.utils import timezone from django.db.models import Q import logging from django_tenants.utils import get_tenant_model, schema_context logger = logging.getLogger(__name__) @shared_task def cleanup_expired_cart_locks(): """ Периодическая задача для очистки истекших блокировок корзины. Освобождает витринные комплекты, которые были добавлены в корзину, но блокировка истекла (timeout 30 минут). Запускается каждые 5 минут (настроить в celery beat schedule). Проходит по всем тенантам. Returns: dict: Статистика очистки { 'released_count': int, # Общее количество освобожденных блокировок 'details': list # Детали по каждому тенанту } """ Tenant = get_tenant_model() overall_stats = { 'released_count': 0, 'details': [] } # Проходим по всем тенантам (кроме public) # Public схема не содержит бизнес-данных (корзины, резервы) tenants = Tenant.objects.exclude(schema_name='public') for tenant in tenants: try: with schema_context(tenant.schema_name): from inventory.models import Reservation # Находим все резервы с истекшей блокировкой expired_locks = Reservation.objects.filter( Q(cart_lock_expires_at__lte=timezone.now()) & Q(cart_lock_expires_at__isnull=False) & Q(status='reserved') ).select_related('product_kit', 'locked_by_user') count = expired_locks.count() if count > 0: affected_kits = list( expired_locks.values_list('product_kit_id', flat=True).distinct() ) logger.info( f"[{tenant.schema_name}] Очистка истекших блокировок: {count} резервов" ) # Очищаем блокировки expired_locks.update( cart_lock_expires_at=None, locked_by_user=None, cart_session_id=None ) overall_stats['released_count'] += count overall_stats['details'].append({ 'tenant': tenant.schema_name, 'released': count, 'kits': affected_kits }) except Exception as e: # Логируем ошибку, но не прерываем обработку других тенантов logger.error(f"[{tenant.schema_name}] Ошибка при очистке блокировок: {e}", exc_info=True) overall_stats['details'].append({ 'tenant': tenant.schema_name, 'error': str(e) }) if overall_stats['released_count'] > 0: logger.info(f"Общая очистка блокировок завершена: {overall_stats}") return overall_stats