LIL-583 реферальная система

remotes/origin/hotfix/LIL-691
gzbender 8 years ago
parent 397bc9c2d3
commit d63709b252
  1. 12
      api/v1/views.py
  2. 7
      apps/auth/views.py
  3. 103
      apps/payment/models.py
  4. 8
      apps/user/models.py
  5. 14
      project/context_processors.py
  6. 1
      project/settings.py
  7. 32
      project/templates/blocks/popup_auth.html
  8. 9
      project/templates/lilcity/index.html
  9. 10
      project/templates/lilcity/main.html
  10. 5
      project/views.py
  11. 8
      web/src/js/modules/api.js
  12. 25
      web/src/js/modules/popup.js

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from rest_framework import status, views, viewsets, generics 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 rest_framework.response import Response
from . import ExtendedModelViewSet from . import ExtendedModelViewSet
@ -429,3 +429,13 @@ class PaymentViewSet(ExtendedModelViewSet):
'amount', 'created_at', 'amount', 'created_at',
) )
search_fields = ('user__email', 'user__first_name', 'user__last_name',) 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))

@ -19,6 +19,7 @@ from django.shortcuts import redirect
from apps.notification.utils import send_email from apps.notification.utils import send_email
from apps.config.models import Config from apps.config.models import Config
from apps.user.models import Referral
from .forms import LearnerRegistrationForm from .forms import LearnerRegistrationForm
from .tokens import verification_email_token from .tokens import verification_email_token
@ -51,6 +52,11 @@ class LearnerRegistrationView(FormView):
user.last_name = last_name user.last_name = last_name
user.set_password(password) 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() user.save()
login(self.request, user) login(self.request, user)
@ -81,6 +87,7 @@ class LoginView(FormView):
def form_valid(self, form): def form_valid(self, form):
login(self.request, form.get_user()) login(self.request, form.get_user())
self.request.session['referrer'] = None
return JsonResponse({"success": True}) return JsonResponse({"success": True})
def form_invalid(self, form): def form_invalid(self, form):

@ -1,5 +1,5 @@
from decimal import Decimal
import arrow import arrow
from paymentwall import Pingback from paymentwall import Pingback
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager from polymorphic.managers import PolymorphicManager
@ -108,6 +108,62 @@ class Payment(PolymorphicModel):
verbose_name_plural = 'Платежи' verbose_name_plural = 'Платежи'
ordering = ('created_at',) 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): def calc_commission(self):
return self.amount * config.SERVICE_COMMISSION / 100 return self.amount * config.SERVICE_COMMISSION / 100
@ -136,7 +192,8 @@ class CoursePayment(Payment):
verbose_name_plural = 'Платежи за курсы' verbose_name_plural = 'Платежи за курсы'
def save(self, *args, **kwargs): 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) super().save(*args, **kwargs)
author_balance = getattr(self, 'authorbalance', None) author_balance = getattr(self, 'authorbalance', None)
if not author_balance: if not author_balance:
@ -148,6 +205,13 @@ class CoursePayment(Payment):
else: else:
author_balance.amount = self.amount author_balance.amount = self.amount
author_balance.save() 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): class SchoolPayment(Payment):
@ -168,33 +232,16 @@ class SchoolPayment(Payment):
return days return days
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
aggregate = SchoolSchedule.objects.filter( amount_data = Payment.calc_amount(school_payment=self)
weekday__in=self.weekdays, self.amount = amount_data.get('amount')
).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
super().save(*args, **kwargs) 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 @property
def date_end_humanize(self): def date_end_humanize(self):

@ -12,7 +12,6 @@ from django.utils.translation import gettext_lazy as _
from api.v1 import serializers from api.v1 import serializers
from apps.notification.utils import send_email from apps.notification.utils import send_email
from apps.user.tasks import user_to_mixpanel from apps.user.tasks import user_to_mixpanel
@ -259,11 +258,16 @@ class EmailSubscription(models.Model):
class Referral(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') referrer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='referrals')
discount = models.IntegerField() discount = models.IntegerField()
referrer_cashback = models.IntegerField() referrer_cashback = models.IntegerField()
payment = models.OneToOneField('payment.Payment', null=True, blank=True, on_delete=models.CASCADE)
class Meta: class Meta:
verbose_name = 'Реферал' verbose_name = 'Реферал'
verbose_name_plural = 'Рефералы' verbose_name_plural = 'Рефералы'
def cashback(self, amount):
# TODO
pass

@ -1,6 +1,10 @@
from django.contrib.auth import get_user_model
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import Baner from apps.content.models import Baner
User = get_user_model()
def config(request): def config(request):
return {"config": Config.load()} return {"config": Config.load()}
@ -8,3 +12,13 @@ def config(request):
def baner(request): def baner(request):
return {'baner': Baner.objects.filter(use=True).first()} 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}

@ -93,6 +93,7 @@ TEMPLATES = [
'context_processors': [ 'context_processors': [
'project.context_processors.config', 'project.context_processors.config',
'project.context_processors.baner', 'project.context_processors.baner',
'project.context_processors.referrer',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',

@ -1,5 +1,5 @@
{% load static %} {% load static %}
<div class="popup js-popup-auth {% if referrer %}open visible{% endif %}"> <div class="popup js-popup-auth {% if is_registration %}open visible{% endif %}">
<div class="popup__wrap js-popup-wrap"> <div class="popup__wrap js-popup-wrap">
<button class="popup__close js-popup-close"> <button class="popup__close js-popup-close">
<svg class="icon icon-close"> <svg class="icon icon-close">
@ -10,25 +10,14 @@
<div class="auth js-auth"> <div class="auth js-auth">
<div class="auth__login js-auth-login"> <div class="auth__login js-auth-login">
<div class="auth__nav"> <div class="auth__nav">
<a class="auth__type js-auth-type {% if not referrer %}active{% endif %}" href="#">Войти</a> <a class="auth__type js-auth-type {% if not is_registration %}active{% endif %}" href="#">Войти</a>
<a class="auth__type js-auth-type {% if referrer %}active{% endif %}" href="#">РЕГИСТРАЦИЯ</a> <a class="auth__type js-auth-type {% if is_registration %}active{% endif %}" href="#">РЕГИСТРАЦИЯ</a>
</div> </div>
<div class="auth__body"> <div class="auth__body">
<form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}"> <form id="learner-auth-form" method="post" action="{% url 'lilcity:login' %}">
{% csrf_token %} {% csrf_token %}
<div class="auth__tab js-auth-tab" style="display: block;"> <div class="auth__tab js-auth-tab" {% if not is_registration %}style="display: block;"{% endif %}>
<div class="auth__enter js-auth-enter"> <div class="auth__enter js-auth-enter">
{% if referrer %}
<input type="hidden" name="referrer" value="{{ referrer.id }}">
<div style="margin: 0 0 20px;">
<a href="{% url 'user' referrer.id %}" target="_blank" style="
font-family: 'ProximaNova-Bold', serif;
font-size: 10px;
letter-spacing: 1px;
text-transform: uppercase;">
{{ referrer.get_full_name }}</a> дарит вам скидку 10% на первую покупку!
</div>
{% endif %}
<div id="learner-auth-field__username" class="auth__field field learner-auth-form__field"> <div id="learner-auth-field__username" class="auth__field field learner-auth-form__field">
<div class="field__label">ПОЧТА</div> <div class="field__label">ПОЧТА</div>
<div class="field__wrap"> <div class="field__wrap">
@ -74,7 +63,18 @@
</form> </form>
<form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}"> <form id="learner-registration-form" method="post" action="{% url 'lilcity:registration-learner' %}">
{% csrf_token %} {% csrf_token %}
<div class="auth__tab js-auth-tab"> <div class="auth__tab js-auth-tab" {% if is_registration %}style="display: block;"{% endif %}>
{% if referrer %}
<input type="hidden" name="referrer" value="{{ referrer.id }}">
<div style="margin: 0 0 20px;">
<a href="{% url 'user' referrer.id %}" target="_blank" style="
font-family: 'ProximaNova-Bold', serif;
font-size: 10px;
letter-spacing: 1px;
text-transform: uppercase;">
{{ referrer.get_full_name }}</a> дарит вам скидку 10% на первую покупку!
</div>
{% endif %}
<div class="auth__fieldset"> <div class="auth__fieldset">
<div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field"> <div id="learner-registration-field__first-name" class="auth__field field learner-registration-form__field">
<div class="field__label">ИМЯ</div> <div class="field__label">ИМЯ</div>

@ -130,6 +130,14 @@
{% include "templates/blocks/popup_course_lock.html" %} {% include "templates/blocks/popup_course_lock.html" %}
{% include "templates/blocks/popup_subscribe.html" %} {% include "templates/blocks/popup_subscribe.html" %}
</div> </div>
<script>
window.STORE = {
accessToken: '{{ request.user.auth_token }}',
user: {
id: '{{ request.user.id }}',
}
};
</script>
<script type="text/javascript" src={% static "app.js" %}></script> <script type="text/javascript" src={% static "app.js" %}></script>
<script> <script>
var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }}); var schoolDiscount = parseFloat({{ config.SERVICE_DISCOUNT }});
@ -140,5 +148,6 @@
(function(w, d, s, h, id) { w.roistatProjectId = id; w.roistatHost = h; var p = d.location.protocol == "https:" ? "https://" : "http://"; var u = /^.*roistat_visit=[^;]+(.*)?$/.test(d.cookie) ? "/dist/module.js" : "/api/site/1.0/"+id+"/init"; var js = d.createElement(s); js.charset="UTF-8"; js.async = 1; js.src = p+h+u; var js2 = d.getElementsByTagName(s)[0]; js2.parentNode.insertBefore(js, js2);})(window, document, 'script', 'cloud.roistat.com', '{% setting "ROISTAT_COUNTER_ID" %}'); (function(w, d, s, h, id) { w.roistatProjectId = id; w.roistatHost = h; var p = d.location.protocol == "https:" ? "https://" : "http://"; var u = /^.*roistat_visit=[^;]+(.*)?$/.test(d.cookie) ? "/dist/module.js" : "/api/site/1.0/"+id+"/init"; var js = d.createElement(s); js.charset="UTF-8"; js.async = 1; js.src = p+h+u; var js2 = d.getElementsByTagName(s)[0]; js2.parentNode.insertBefore(js, js2);})(window, document, 'script', 'cloud.roistat.com', '{% setting "ROISTAT_COUNTER_ID" %}');
</script> </script>
{% block foot %}{% endblock foot %} {% block foot %}{% endblock foot %}
{% block body_js %}{% endblock body_js %}
</body> </body>
</html> </html>

@ -13,3 +13,13 @@
{% include "templates/blocks/teachers.html" %} {% include "templates/blocks/teachers.html" %}
{% include "templates/blocks/partners.html" %} {% include "templates/blocks/partners.html" %}
{% endblock content %} {% endblock content %}
{% block body_js %}
{% if is_registration %}
<script>
$(document).ready(function () {
$('.js-header-enter').click();
});
</script>
{% endif %}
{% endblock body_js %}

@ -75,12 +75,13 @@ class IndexView(TemplateView):
if referrer: if referrer:
try: try:
referrer = short_url.decode_url(referrer) referrer = short_url.decode_url(referrer)
referrer = User.objects.get(pk=referrer) referrer = User.objects.get(pk=referrer).id
except: except:
referrer = None referrer = None
self.request.session['referrer'] = referrer
context.update({ context.update({
'referrer': referrer, 'is_registration': bool(referrer),
'online': online, 'online': online,
'online_coming_soon': online_coming_soon, 'online_coming_soon': online_coming_soon,
'school_schedule': school_schedule, 'school_schedule': school_schedule,

@ -502,4 +502,12 @@ export const api = {
} }
}); });
}, },
getPaymentAmount: (params) => {
return api.get('/api/v1/payments/calc-amount', {
params: params,
headers: {
'Authorization': `Token ${window.STORE.accessToken}`,
}
});
}
}; };

@ -1,9 +1,10 @@
import $ from 'jquery'; import $ from 'jquery';
import {api} from './api';
var selectedWeekdays = {}; var selectedWeekdays = {};
$(document).ready(function () { $(document).ready(function () {
let body = $('body'), let body = $('body'),
popup; popup = $('.popup.visible.open');
body.on('click', '[data-popup]', function(e){ body.on('click', '[data-popup]', function(e){
e.preventDefault(); e.preventDefault();
@ -100,6 +101,7 @@ $(document).ready(function () {
}); });
function updateCart(){ function updateCart(){
var $orderPrice = $('.order_price_text');
var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье']; var days = ['', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'];
var weekdays = [], daysText = [], price = 0; var weekdays = [], daysText = [], price = 0;
for(var i in selectedWeekdays) { for(var i in selectedWeekdays) {
@ -108,13 +110,22 @@ $(document).ready(function () {
daysText.push(days[i]); daysText.push(days[i]);
} }
var text = ''; if(weekdays.length){
if(schoolAmountForDiscount <= price) { api.getPaymentAmount({ user: window.STORE.user.id, weekdays: weekdays })
text = '<del>'+price+'</del> '+(price-schoolDiscount)+'р.'; .then((response) => {
} else { var text = '';
text = price+'p.'; if(response.data.price != response.data.amount) {
text = '<del>'+response.data.price+'</del> '+response.data.amount+'р.';
} else {
text = response.data.amount+'p.';
}
$orderPrice.html(text);
});
} }
$('.order_price_text').html(text); else {
$orderPrice.html('0p.');
}
$('.order__days').html((daysText.length) ? daysText.join(', '):'Ничего не выбрано'); $('.order__days').html((daysText.length) ? daysText.join(', '):'Ничего не выбрано');
var link = $('.but_btn_popup').data('link'); var link = $('.but_btn_popup').data('link');

Loading…
Cancel
Save