uchill/backend/apps/referrals/management/commands/process_pending_referral_bo...

82 lines
4.1 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.

"""
Обработка отложенных бонусов за рефералов.
Начисление возможно только при выполнении одного из условий:
- Прошло 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}'))