diff --git a/api/v1/views.py b/api/v1/views.py index 41cd881a..271d907f 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -1,7 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework import status, views, viewsets, generics -from rest_framework.decorators import detail_route, list_route +from rest_framework.decorators import detail_route, list_route, action from rest_framework.response import Response from . import ExtendedModelViewSet @@ -429,3 +429,13 @@ class PaymentViewSet(ExtendedModelViewSet): 'amount', 'created_at', ) search_fields = ('user__email', 'user__first_name', 'user__last_name',) + + @action(methods=['get'], detail=False, url_path='calc-amount') + def calc_amount(self, request, pk=None): + user = request.query_params.get('user') + course = request.query_params.get('course') + weekdays = request.query_params.getlist('weekdays[]') + user = user and User.objects.get(pk=user) + course = course and Course.objects.get(pk=course) + + return Response(Payment.calc_amount(user=user, course=course, weekdays=weekdays)) diff --git a/apps/auth/views.py b/apps/auth/views.py index f6105e75..284d0dd5 100644 --- a/apps/auth/views.py +++ b/apps/auth/views.py @@ -19,6 +19,7 @@ from django.shortcuts import redirect from apps.notification.utils import send_email from apps.config.models import Config +from apps.user.models import Referral from .forms import LearnerRegistrationForm from .tokens import verification_email_token @@ -51,6 +52,11 @@ class LearnerRegistrationView(FormView): user.last_name = last_name user.set_password(password) + referrer = self.request.session.get('referrer') + if referrer: + Referral.objects.create(referral=user, referrer=referrer, discount=Config.REFERRAL_DISCOUNT, + referrer_cashback=Config.REFERRER_CASHBACK) + self.request.session['referrer'] = None user.save() login(self.request, user) @@ -81,6 +87,7 @@ class LoginView(FormView): def form_valid(self, form): login(self.request, form.get_user()) + self.request.session['referrer'] = None return JsonResponse({"success": True}) def form_invalid(self, form): diff --git a/apps/payment/models.py b/apps/payment/models.py index 32d7a0e9..17c75bb1 100644 --- a/apps/payment/models.py +++ b/apps/payment/models.py @@ -1,5 +1,5 @@ +from decimal import Decimal import arrow - from paymentwall import Pingback from polymorphic.models import PolymorphicModel from polymorphic.managers import PolymorphicManager @@ -108,6 +108,62 @@ class Payment(PolymorphicModel): verbose_name_plural = 'Платежи' ordering = ('created_at',) + @classmethod + def calc_amount(cls, course_payment=None, school_payment=None, user=None, course=None, weekdays=None, + add_days=False, date_start=None, date_end=None): + if course_payment: + course = course_payment.course + user = course_payment.user + if school_payment: + user = school_payment.user + weekdays = school_payment.weekdays + add_days = school_payment.add_days + date_start = school_payment.date_start + date_end = school_payment.date_end + referral_discount = 0 + referrer_cashback = 0 + if hasattr(user, 'referral') and not user.referral.payment: + referral_discount = user.referral.discount + referrer_cashback = user.referral.referrer_cashback + discount = 0 + if course: + price = course.price + else: + aggregate = SchoolSchedule.objects.filter( + weekday__in=weekdays, + ).aggregate( + models.Sum('month_price'), + ) + if add_days: + _school_payment = SchoolPayment.objects.filter( + add_days=False, + date_start__lte=date_start, + date_end__gte=date_end, + status__in=[ + Pingback.PINGBACK_TYPE_REGULAR, + Pingback.PINGBACK_TYPE_GOODWILL, + Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, + ], + ).last() + weekday_count = weekday_in_date_range(date_start, date_end, weekdays[0]) + all_weekday_count = weekday_in_date_range(_school_payment.date_start, _school_payment.date_end, + weekdays[0]) + price = Decimal(aggregate.get('month_price__sum', 0) * weekday_count // all_weekday_count) + else: + price = Decimal(aggregate.get('month_price__sum', 0)) + if price >= config.SERVICE_DISCOUNT_MIN_AMOUNT: + discount = config.SERVICE_DISCOUNT + referral_discount = (price - discount) * referral_discount / 100 + referrer_cashback = (price - discount) * referrer_cashback / 100 + amount = price - discount - referral_discount + return { + 'price': price, + 'amount': amount, + 'referral_discount': referral_discount, + 'referrer_cashback': referrer_cashback, + 'discount': discount, + } + def calc_commission(self): return self.amount * config.SERVICE_COMMISSION / 100 @@ -136,7 +192,8 @@ class CoursePayment(Payment): verbose_name_plural = 'Платежи за курсы' def save(self, *args, **kwargs): - self.amount = self.course.price + amount_data = Payment.calc_amount(course_payment=self) + self.amount = amount_data.get('amount') super().save(*args, **kwargs) author_balance = getattr(self, 'authorbalance', None) if not author_balance: @@ -148,6 +205,13 @@ class CoursePayment(Payment): else: author_balance.amount = self.amount author_balance.save() + # Если юзер реферал и нет платежа, где применялась скидка + if hasattr(self.user, 'referral') and not self.user.referral.payment: + # Отправляем кэшбэк + self.user.referral.cashback(amount_data.get('referrer_cashback')) + # Платеж - как сигнал, что скидка применилась + self.user.referral.payment = self + self.user.referral.save() class SchoolPayment(Payment): @@ -168,33 +232,16 @@ class SchoolPayment(Payment): return days def save(self, *args, **kwargs): - aggregate = SchoolSchedule.objects.filter( - weekday__in=self.weekdays, - ).aggregate( - models.Sum('month_price'), - ) - if self.add_days: - _school_payment = SchoolPayment.objects.filter( - add_days=False, - date_start__lte=self.date_start, - date_end__gte=self.date_end, - status__in=[ - Pingback.PINGBACK_TYPE_REGULAR, - Pingback.PINGBACK_TYPE_GOODWILL, - Pingback.PINGBACK_TYPE_RISK_REVIEWED_ACCEPTED, - ], - ).last() - weekday_count = weekday_in_date_range(self.date_start, self.date_end, self.weekdays[0]) - all_weekday_count = weekday_in_date_range(_school_payment.date_start, _school_payment.date_end, self.weekdays[0]) - month_price_sum = aggregate.get('month_price__sum', 0) * weekday_count // all_weekday_count - else: - month_price_sum = aggregate.get('month_price__sum', 0) - if month_price_sum >= config.SERVICE_DISCOUNT_MIN_AMOUNT: - discount = config.SERVICE_DISCOUNT - else: - discount = 0 - self.amount = month_price_sum - discount + amount_data = Payment.calc_amount(school_payment=self) + self.amount = amount_data.get('amount') super().save(*args, **kwargs) + # Если юзер реферал и нет платежа, где применялась скидка + if not self.add_days and hasattr(self.user, 'referral') and not self.user.referral.payment: + # Отправляем кэшбэк + self.user.referral.cashback(amount_data.get('referrer_cashback')) + # Платеж - как сигнал, что скидка применилась + self.user.referral.payment = self + self.user.referral.save() @property def date_end_humanize(self): diff --git a/apps/user/models.py b/apps/user/models.py index 4884c918..286c61c3 100644 --- a/apps/user/models.py +++ b/apps/user/models.py @@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _ from api.v1 import serializers from apps.notification.utils import send_email - from apps.user.tasks import user_to_mixpanel @@ -259,11 +258,16 @@ class EmailSubscription(models.Model): class Referral(models.Model): - referral = models.OneToOneField(User, on_delete=models.CASCADE) + referral = models.OneToOneField(User, on_delete=models.CASCADE, related_name='referral') referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='referrals') discount = models.IntegerField() referrer_cashback = models.IntegerField() + payment = models.OneToOneField('payment.Payment', null=True, blank=True, on_delete=models.CASCADE) class Meta: verbose_name = 'Реферал' verbose_name_plural = 'Рефералы' + + def cashback(self, amount): + # TODO + pass diff --git a/project/context_processors.py b/project/context_processors.py index 0e7e9d32..2af515c3 100644 --- a/project/context_processors.py +++ b/project/context_processors.py @@ -1,6 +1,10 @@ +from django.contrib.auth import get_user_model + from apps.config.models import Config from apps.content.models import Baner +User = get_user_model() + def config(request): return {"config": Config.load()} @@ -8,3 +12,13 @@ def config(request): def baner(request): return {'baner': Baner.objects.filter(use=True).first()} + + +def referrer(request): + referrer = request.session.get('referrer') + if referrer: + try: + referrer = User.objects.get(pk=referrer) + except: + referrer = None + return {'referrer': referrer} diff --git a/project/settings.py b/project/settings.py index 9323745c..b5a947cc 100644 --- a/project/settings.py +++ b/project/settings.py @@ -93,6 +93,7 @@ TEMPLATES = [ 'context_processors': [ 'project.context_processors.config', 'project.context_processors.baner', + 'project.context_processors.referrer', 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', diff --git a/project/templates/blocks/popup_auth.html b/project/templates/blocks/popup_auth.html index bce10d61..058a2205 100644 --- a/project/templates/blocks/popup_auth.html +++ b/project/templates/blocks/popup_auth.html @@ -1,5 +1,5 @@ {% load static %} -