""" Обработка отложенных бонусов за рефералов. Начисление возможно только при выполнении одного из условий: - Прошло 30+ дней с приглашения И реферал был активен 20+ дней; - Реферал был активен 21+ день (независимо от срока). Запуск: python manage.py process_pending_referral_bonuses Рекомендуется добавить в cron (ежедневно). """ from django.core.management.base import BaseCommand from django.utils import timezone from django.db import transaction from apps.referrals.models import ( PendingReferralBonus, UserActivityDay, UserReferralProfile, ) class Command(BaseCommand): help = 'Начислить бонусы за рефералов, выполнивших условия по активности (20+ дней за 30 дней или 21+ день всего)' def add_arguments(self, parser): parser.add_argument( '--dry-run', action='store_true', help='Только показать, что было бы начислено, без изменений в БД', ) def handle(self, *args, **options): dry_run = options['dry_run'] now = timezone.now() paid_count = 0 for pending in PendingReferralBonus.objects.filter(status=PendingReferralBonus.STATUS_PENDING).select_related( 'referrer', 'referred_user' ): referred_at = pending.referred_at referred_date = referred_at.date() # Дней активности реферала с даты приглашения active_days = UserActivityDay.objects.filter( user=pending.referred_user, date__gte=referred_date, ).count() days_since_referral = (now - referred_at).days past_30 = days_since_referral >= 30 # Условие: (30+ дней и 20+ активных) ИЛИ (21+ активных дней) if (past_30 and active_days >= 20) or (active_days >= 21): if dry_run: self.stdout.write( f'[dry-run] Начислили бы {pending.points} очков {pending.referrer.email} ' f'за реферала {pending.referred_user.email} (активных дней: {active_days}, прошло дней: {days_since_referral})' ) paid_count += 1 continue try: with transaction.atomic(): referrer_profile = pending.referrer.referral_profile referrer_profile.add_points(pending.points, reason=pending.reason or f'Реферал {pending.referred_user.email} выполнил условия активности') pending.status = PendingReferralBonus.STATUS_PAID pending.paid_at = now pending.save(update_fields=['status', 'paid_at']) paid_count += 1 self.stdout.write( self.style.SUCCESS( f'Начислено {pending.points} очков {pending.referrer.email} за {pending.referred_user.email} (активных дней: {active_days})' ) ) except UserReferralProfile.DoesNotExist: self.stdout.write( self.style.WARNING(f'Пропуск {pending.id}: у реферера нет профиля') ) except Exception as e: self.stdout.write( self.style.ERROR(f'Ошибка при начислении {pending.id}: {e}') ) if dry_run: self.stdout.write(self.style.SUCCESS(f'[dry-run] Всего к начислению: {paid_count}')) else: self.stdout.write(self.style.SUCCESS(f'Начислено бонусов: {paid_count}'))