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