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. 12
      lms/celery.py
  9. 11
      lms/settings.py
  10. 1
      progress/views.py

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

@ -72,7 +72,7 @@ class Topic(models.Model):
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,
level=None, direction=None, **kwargs):
@ -102,10 +102,11 @@ class CourseManager(models.Manager):
kwargs['direction'] = get_real_name(COURSE_DIRECTION, direction[0])
try:
course = self.get(slug=slug)
course = self.get(slug=old_slug)
for i in kwargs:
if kwargs[i]:
setattr(course, i, kwargs[i])
course.slug = slug
course.save()
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
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models
from django.utils import timezone
from yandex_money.models import Payment
import logging
from progress.models import Progress
logger_business_rules = logging.getLogger('business_rules')
class Bill(models.Model):
@ -13,10 +22,75 @@ class Bill(models.Model):
blank=True, editable=False)
description = models.TextField(verbose_name='Внутренняя заметка', blank=True)
date = models.DateTimeField(verbose_name="Дата выставления", auto_now_add=True)
freeze = models.BooleanField(verbose_name='Отказ от платежей', default=False)
def __str__(self):
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):
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)
bill = models.ForeignKey(to=Bill, 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):
return '''Вам выставлен счёт,''' if \
@ -86,7 +162,19 @@ class Invoice(models.Model):
class InvoiceRebilling(Invoice):
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:
verbose_name = 'Повторный платёж'

@ -1,79 +1,40 @@
import json
from datetime import datetime, timedelta
import logging
import os
import requests
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from yandex_money.models import Payment
from finance.models import Invoice
from finance.models import InvoiceRebilling
from lms import celery_app
from django.conf import settings
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
def periodic_billing(order_number):
try:
sample = Invoice.objects.get(yandex_pay__order_number=order_number)
except Invoice.DoesNotExist:
raise ValueError('Номер заказа {} не найден'.format(order_number))
bill = sample.bill
invoice = Invoice.objects.create(
status='P',
price=sample.price,
method=sample.method,
rebilling=True,
bill=bill
)
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 periodic_billing():
logger_yandex.info("start periodic billing task")
# for invoice in InvoiceRebilling.objects.filter(method='Y').exclude(status='F'):
#
# user = invoice.bill.user
# yandex_pay = Payment.objects.create(
# 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):
resp = requests.post(settings.YANDEX_MONEY_MWS_URL + 'repeatCardPayment',
data={
'clientOrderId': invoice.id, # уникальное возрастающее целое число
'invoiceId': invoice.yandex_pay.invoice_id,
'invoiceId': invoice.key,
'amount': invoice.price,
'orderNumber': invoice.yandex_pay.order_number
},

@ -3,6 +3,8 @@ from finance import views
urlpatterns = [
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/([0-9]{1,99})/$', views.BillDetailView.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 finance.models import Bill, Invoice, InvoiceRebilling
from finance.serializers import BillSerializer, InvoiceSerializer
from finance.tasks import setup_periodic_billing
from lms.global_decorators import transaction_decorator
from lms.tools import get_real_name
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):
renderer_classes = (JSONRenderer,)
@ -109,8 +140,7 @@ class InvoiceDetailView(APIView):
price = request.JSON.get('price', None)
comment = request.JSON.get('comment', None)
real_price = request.JSON.get('real_price', None)
rebilling_on = request.JSON.get('is_rebilling', False)
pay_count = request.JSON.get('pay_count', None)
pay_count = int(request.JSON.get('pay_count', '1'))
if bill_id is None:
return Response("Не передан id счёта", status=400)
@ -129,14 +159,15 @@ class InvoiceDetailView(APIView):
if bill.check_validate(invoice_id) and is_open:
return Response("Уже есть платёж открывающий курс", status=400)
if rebilling_on:
if pay_count > 1:
invoice = InvoiceRebilling.objects.create(
bill=bill,
method=method,
status=status,
is_open=is_open,
pay_count=pay_count,
rebilling_on=True,
)
else:
try:
invoice = Invoice.objects.get(id=invoice_id)
@ -172,6 +203,9 @@ class InvoiceDetailView(APIView):
invoice.is_open = is_open
invoice.comment = comment
if pay_count > 1:
invoice.create_child_pays(pay_count)
if invoice.method == 'Y' and invoice.yandex_pay is None:
yandex_pay = Payment.objects.create(
order_amount=invoice.price,
@ -427,6 +461,9 @@ class YandexAvisoView(APIView):
})
pay.shop_amount = data['shopSumAmount']
invoice = pay.invoice
invoice.key = data['invoiceId']
invoice.save()
pay.status = Payment.STATUS.SUCCESS
now = timezone.now()
pay.performed_datetime = now.isoformat()
@ -457,12 +494,6 @@ class YandexAvisoView(APIView):
msg.attach_alternative(html_content, "text/html")
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')
@ -470,7 +501,7 @@ class YandexFailView(APIView):
renderer_classes = (JSONRenderer,)
@staticmethod
def post(request):
def get(request):
data = dict()
for i in request.body.decode('utf-8').split('&'):
key = i.split('=')[0]

@ -3,6 +3,8 @@ import os
from celery import Celery
from raven import Client
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.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.settings')
@ -13,10 +15,18 @@ app.config_from_object('django.conf:settings', namespace='CELERY')
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_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)
def debug_task(self):

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

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

Loading…
Cancel
Save