Merge branch 'dev' into 'master'

Dev

See merge request !393
master
Andrey 8 years ago
commit 10a40fac71
  1. 1
      access/views.py
  2. 5
      courses/models.py
  3. 40
      finance/migrations/0010_auto_20180412_1628.py
  4. 92
      finance/models.py
  5. 75
      finance/tasks.py
  6. 2
      finance/urls.py
  7. 55
      finance/views.py
  8. 14
      lms/celery.py
  9. 11
      lms/settings.py
  10. 1
      progress/views.py

@ -128,6 +128,7 @@ class FindUserView(APIView):
class DetailUserView(APIView): class DetailUserView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
permission_classes = (permissions.IsAuthenticated,)
@staticmethod @staticmethod
def post(request, out_key=None): def post(request, out_key=None):

@ -72,7 +72,7 @@ class Topic(models.Model):
class CourseManager(models.Manager): class CourseManager(models.Manager):
def update_or_create_course(self, image=None, big_image=None, statistic=None, def update_or_create_course(self, image=None, big_image=None, statistic=None, old_slug=None,
big_mobile_image=None, slug=None, teacher_tokens=None, big_mobile_image=None, slug=None, teacher_tokens=None,
level=None, direction=None, **kwargs): level=None, direction=None, **kwargs):
@ -102,10 +102,11 @@ class CourseManager(models.Manager):
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction[0]) kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction[0])
try: try:
course = self.get(slug=slug) course = self.get(slug=old_slug)
for i in kwargs: for i in kwargs:
if kwargs[i]: if kwargs[i]:
setattr(course, i, kwargs[i]) setattr(course, i, kwargs[i])
course.slug = slug
course.save() course.save()
except ObjectDoesNotExist: except ObjectDoesNotExist:

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-12 16:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('finance', '0009_invoicerebilling_pay_count'),
]
operations = [
migrations.RemoveField(
model_name='invoicerebilling',
name='pay_count',
),
migrations.AddField(
model_name='bill',
name='freeze',
field=models.BooleanField(default=False, verbose_name='Отказ от платежей'),
),
migrations.AddField(
model_name='invoice',
name='date_of_payment',
field=models.DateTimeField(blank=True, null=True, verbose_name='Дата фактической оплаты'),
),
migrations.AddField(
model_name='invoice',
name='expected_date',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Ожидаемая дата платежа'),
),
migrations.AlterField(
model_name='invoice',
name='date',
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания платежа'),
),
]

@ -1,8 +1,17 @@
# coding=utf-8 # coding=utf-8
from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
from django.db import models from django.db import models
from django.utils import timezone
from yandex_money.models import Payment from yandex_money.models import Payment
import logging
from progress.models import Progress
logger_business_rules = logging.getLogger('business_rules')
class Bill(models.Model): class Bill(models.Model):
@ -13,10 +22,75 @@ class Bill(models.Model):
blank=True, editable=False) blank=True, editable=False)
description = models.TextField(verbose_name='Внутренняя заметка', blank=True) description = models.TextField(verbose_name='Внутренняя заметка', blank=True)
date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True) date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True)
freeze = models.BooleanField(verbose_name='Отказ от платежей', default=False)
def __str__(self): def __str__(self):
return '%s: %s' % (self.id, self.user) return '%s: %s' % (self.id, self.user)
def freeze_course(self):
if self.invoice_set.exclude(status='F').exists():
log = False
try:
p = Progress.objects.get(user=self.user, course_token=str(self.course_token))
p.is_freeze = True
p.save()
except Progress.DoesNotExist:
log = True
if log:
logger_business_rules.info('Отказ от платежей прошёл успешно', exc_info=True, extra={
'description': 'The privileges were not taken away, as they were not granted',
'user': self.user,
})
msg = EmailMessage(
'Вы откозались от оплаты по счёту',
"""Вы откозались от оплаты по счёту.
Вы сможете продолжить оплату в личном кабинете""",
to=[self.user.email],
bcc=[self.opener.email],
reply_to=[self.opener.email],
)
msg.send()
self.freeze = True
self.save()
else:
logger_business_rules.warning('Попытка нарушения правила отказа от платежей', exc_info=True, extra={
'description': 'All payments already paid',
'user': self.user,
})
def unfreeze_course(self, force=False):
if force or self.invoice_set.exclude(status='F')\
.filter(expected_date__lt=timezone.now() + relativedelta(days=1)).exists():
if self.invoice_set.filter(status='F').exclude(expected_date__lt=timezone.now()).exists():
try:
p = Progress.objects.get(user=self.user, course_token=str(self.course_token))
p.is_freeze = False
p.save()
except Progress.DoesNotExist:
pass
msg = EmailMessage(
'Вы возобновили оплату по счёту',
"""Вы возобновили оплату по счёту.""",
to=[self.user.email],
bcc=[self.opener.email],
reply_to=[self.opener.email],
)
msg.send()
self.freeze = False
self.save()
else:
logger_business_rules.warning('Попытка нарушения правила возобновления платежей', exc_info=True, extra={
'description': 'Excepted date more than one day',
'user': self.user,
})
def get_full_price(self): def get_full_price(self):
return sum([i.price for i in self.invoice_set.all() if not i.price is None]) return sum([i.price for i in self.invoice_set.all() if not i.price is None])
@ -59,7 +133,9 @@ class Invoice(models.Model):
blank=True, editable=False) blank=True, editable=False)
bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт") bill = models.ForeignKey(to=Bill, verbose_name="Связный счёт")
is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс") is_open = models.BooleanField(default=True, verbose_name="Открывает ли платёж курс")
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания платежа")
expected_date = models.DateTimeField(default=timezone.now, verbose_name="Ожидаемая дата платежа")
date_of_payment = models.DateTimeField(verbose_name="Дата фактической оплаты", blank=True, null=True)
def get_comment(self): def get_comment(self):
return '''Вам выставлен счёт,''' if \ return '''Вам выставлен счёт,''' if \
@ -86,7 +162,19 @@ class Invoice(models.Model):
class InvoiceRebilling(Invoice): class InvoiceRebilling(Invoice):
rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False) rebilling_on = models.BooleanField(verbose_name='Повторять платеж', default=False, editable=False)
pay_count = models.SmallIntegerField(verbose_name='Всего платежей', editable=False)
def create_child_pays(self, count):
for idx in range(int(count)-1):
InvoiceRebilling.objects.create(
bill=self.bill,
comment=self.comment,
method=self.method,
status='W',
is_open=self.is_open,
price=self.price,
rebilling_on=False,
expected_date=(timezone.now() + relativedelta(months=idx+1)),
)
class Meta: class Meta:
verbose_name = 'Повторный платёж' verbose_name = 'Повторный платёж'

@ -1,79 +1,40 @@
import json
from datetime import datetime, timedelta
import logging import logging
import os import os
import requests import requests
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from yandex_money.models import Payment from yandex_money.models import Payment
from finance.models import Invoice from finance.models import InvoiceRebilling
from lms import celery_app from lms import celery_app
from django.conf import settings from django.conf import settings
logger_yandex = logging.getLogger('yandex_money') logger_yandex = logging.getLogger('yandex_money')
def setup_periodic_billing(order_number):
# TODO: настроить периодичность и срок окончания
# 12:00 первого числа каждого месяца
schedule, _ = CrontabSchedule.objects.get_or_create(
minute='0',
hour='12',
day_of_week='*',
day_of_month='1',
month_of_year='*'
)
PeriodicTask.objects.create(
crontab=schedule,
name='Periodic billing (order_number={})'.format(order_number),
task='finance.tasks.periodic_billing',
kwargs=json.dumps({
'order_number': order_number
}),
expires=datetime.utcnow() + timedelta(days=180) # в течение полугода
)
@celery_app.task @celery_app.task
def periodic_billing(order_number): def periodic_billing():
try: logger_yandex.info("start periodic billing task")
sample = Invoice.objects.get(yandex_pay__order_number=order_number)
except Invoice.DoesNotExist: # for invoice in InvoiceRebilling.objects.filter(method='Y').exclude(status='F'):
raise ValueError('Номер заказа {} не найден'.format(order_number)) #
# user = invoice.bill.user
bill = sample.bill # yandex_pay = Payment.objects.create(
# order_amount=invoice.price,
invoice = Invoice.objects.create( # customer_number=user.id,
status='P', # user=user,
price=sample.price, # cps_email=user.email
method=sample.method, # )
rebilling=True, # invoice.yandex_pay = yandex_pay
bill=bill # invoice.save()
) #
# repeat_card_payment(invoice)
if invoice.method == 'Y':
user = bill.user
yandex_pay = Payment.objects.create(
invoice_id=sample.yandex_pay.invoice_id,
order_amount=invoice.price,
customer_number=user.id,
user=user,
cps_email=user.email
)
invoice.yandex_pay = yandex_pay
invoice.save()
repeat_card_payment(invoice)
def repeat_card_payment(invoice): def repeat_card_payment(invoice):
resp = requests.post(settings.YANDEX_MONEY_MWS_URL + 'repeatCardPayment', resp = requests.post(settings.YANDEX_MONEY_MWS_URL + 'repeatCardPayment',
data={ data={
'clientOrderId': invoice.id, # уникальное возрастающее целое число 'clientOrderId': invoice.id, # уникальное возрастающее целое число
'invoiceId': invoice.yandex_pay.invoice_id, 'invoiceId': invoice.key,
'amount': invoice.price, 'amount': invoice.price,
'orderNumber': invoice.yandex_pay.order_number 'orderNumber': invoice.yandex_pay.order_number
}, },

@ -3,6 +3,8 @@ from finance import views
urlpatterns = [ urlpatterns = [
url(r'payment/([0-9]{1,99})/$', views.YandexPay.as_view()), url(r'payment/([0-9]{1,99})/$', views.YandexPay.as_view()),
url(r'bill/([0-9]{1,99})/freeze/$', views.FreezeView.as_view()),
url(r'bill/([0-9]{1,99})/unfreeze/$', views.UnFreezeView.as_view()),
url(r'bills/$', views.BillListView.as_view()), url(r'bills/$', views.BillListView.as_view()),
url(r'bills/([0-9]{1,99})/$', views.BillDetailView.as_view()), url(r'bills/([0-9]{1,99})/$', views.BillDetailView.as_view()),
url(r'bills_find/$', views.FindBillView.as_view()), url(r'bills_find/$', views.FindBillView.as_view()),

@ -21,7 +21,6 @@ from courses.models import Course
from courses.api import CourseParamsApi from courses.api import CourseParamsApi
from finance.models import Bill, Invoice, InvoiceRebilling from finance.models import Bill, Invoice, InvoiceRebilling
from finance.serializers import BillSerializer, InvoiceSerializer from finance.serializers import BillSerializer, InvoiceSerializer
from finance.tasks import setup_periodic_billing
from lms.global_decorators import transaction_decorator from lms.global_decorators import transaction_decorator
from lms.tools import get_real_name from lms.tools import get_real_name
from django.utils import timezone from django.utils import timezone
@ -39,6 +38,38 @@ def test_pay(request):
}) })
class FreezeView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request, pk):
try:
bill = Bill.objects.get(id=pk)
except Bill.DoesNotExist:
return Response("Счёт не найден", status=404)
if request.user.is_authenticated and request.user.email == bill.user.email:
bill.freeze_course()
return Response(status=204)
return Response("Permission denied", status=403)
class UnFreezeView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request, pk):
try:
bill = Bill.objects.get(id=pk)
except Bill.DoesNotExist:
return Response("Счёт не найден", status=404)
if request.user.is_authenticated and request.user.email == bill.user.email:
bill.unfreeze_course()
return Response(status=204)
return Response("Permission denied", status=403)
class BillListView(APIView): class BillListView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@ -109,8 +140,7 @@ class InvoiceDetailView(APIView):
price = request.JSON.get('price', None) price = request.JSON.get('price', None)
comment = request.JSON.get('comment', None) comment = request.JSON.get('comment', None)
real_price = request.JSON.get('real_price', None) real_price = request.JSON.get('real_price', None)
rebilling_on = request.JSON.get('is_rebilling', False) pay_count = int(request.JSON.get('pay_count', '1'))
pay_count = request.JSON.get('pay_count', None)
if bill_id is None: if bill_id is None:
return Response("Не передан id счёта", status=400) return Response("Не передан id счёта", status=400)
@ -129,14 +159,15 @@ class InvoiceDetailView(APIView):
if bill.check_validate(invoice_id) and is_open: if bill.check_validate(invoice_id) and is_open:
return Response("Уже есть платёж открывающий курс", status=400) return Response("Уже есть платёж открывающий курс", status=400)
if rebilling_on: if pay_count > 1:
invoice = InvoiceRebilling.objects.create( invoice = InvoiceRebilling.objects.create(
bill=bill, bill=bill,
method=method, method=method,
status=status, status=status,
is_open=is_open, is_open=is_open,
pay_count=pay_count, rebilling_on=True,
) )
else: else:
try: try:
invoice = Invoice.objects.get(id=invoice_id) invoice = Invoice.objects.get(id=invoice_id)
@ -172,6 +203,9 @@ class InvoiceDetailView(APIView):
invoice.is_open = is_open invoice.is_open = is_open
invoice.comment = comment invoice.comment = comment
if pay_count > 1:
invoice.create_child_pays(pay_count)
if invoice.method == 'Y' and invoice.yandex_pay is None: if invoice.method == 'Y' and invoice.yandex_pay is None:
yandex_pay = Payment.objects.create( yandex_pay = Payment.objects.create(
order_amount=invoice.price, order_amount=invoice.price,
@ -427,6 +461,9 @@ class YandexAvisoView(APIView):
}) })
pay.shop_amount = data['shopSumAmount'] pay.shop_amount = data['shopSumAmount']
invoice = pay.invoice
invoice.key = data['invoiceId']
invoice.save()
pay.status = Payment.STATUS.SUCCESS pay.status = Payment.STATUS.SUCCESS
now = timezone.now() now = timezone.now()
pay.performed_datetime = now.isoformat() pay.performed_datetime = now.isoformat()
@ -457,12 +494,6 @@ class YandexAvisoView(APIView):
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
msg.send() msg.send()
try:
InvoiceRebilling.objects.get(yandex_pay=pay)
setup_periodic_billing(pay.order_number)
except InvoiceRebilling.DoesNotExist:
pass
return HttpResponse(xml_res, content_type='application/xml') return HttpResponse(xml_res, content_type='application/xml')
@ -470,7 +501,7 @@ class YandexFailView(APIView):
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod
def post(request): def get(request):
data = dict() data = dict()
for i in request.body.decode('utf-8').split('&'): for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0] key = i.split('=')[0]

@ -3,6 +3,8 @@ import os
from celery import Celery from celery import Celery
from raven import Client from raven import Client
from raven.contrib.celery import register_signal, register_logger_signal from raven.contrib.celery import register_signal, register_logger_signal
from celery.schedules import crontab
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.settings')
@ -13,11 +15,19 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks() app.autodiscover_tasks()
client = Client('http://caaea487274f4e23a9107862484c79f3:3d463ad4717942508536f7a659921950@sentry.skillbox.ru/3') client = Client('http://caaea487274f4e23a9107862484c79f3:3d463ad4717942508536f7a659921950@sentry.skillbox.ru/7')
register_logger_signal(client) register_logger_signal(client)
register_signal(client) register_signal(client)
app.conf.beat_schedule = {
'periodic_billing': {
'schedule': crontab(minute='*/1'),
# crontab(minute='0',hour='*/3',),
'task': 'finance.tasks.periodic_billing'
}
}
@app.task(bind=True) @app.task(bind=True)
def debug_task(self): def debug_task(self):
print('Request: {0!r}'.format(self.request)) print('Request: {0!r}'.format(self.request))

@ -5,6 +5,7 @@ import raven
import environ import environ
import socket import socket
root = environ.Path(__file__) - 2 root = environ.Path(__file__) - 2
env = environ.Env() env = environ.Env()
@ -212,6 +213,11 @@ LOGGING = {
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'yandex'}, 'tags': {'custom-tag': 'yandex'},
}, },
'business_rules': {
'level': 'DEBUG',
'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
'tags': {'custom-tag': 'business_rules'},
},
'console': { 'console': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
@ -228,6 +234,11 @@ LOGGING = {
'level': 'DEBUG', 'level': 'DEBUG',
'propagate': False 'propagate': False
}, },
'business_rules': {
'handlers': ['business_rules'],
'level': 'DEBUG',
'propagate': False
},
}, },
} }

@ -93,6 +93,7 @@ class CourseProgressUserView(APIView):
class TeacherUpdateProgress(APIView): class TeacherUpdateProgress(APIView):
permission_classes = (permissions.IsAuthenticated, )
renderer_classes = (JSONRenderer,) renderer_classes = (JSONRenderer,)
@staticmethod @staticmethod

Loading…
Cancel
Save