You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
493 lines
24 KiB
493 lines
24 KiB
# coding=utf-8
|
|
import json
|
|
import urllib
|
|
import datetime
|
|
from simplejson import JSONDecodeError
|
|
|
|
from django.db import models
|
|
import django.utils.timezone
|
|
from access.models import User, TrafHistory
|
|
from lms.settings import BILL_URL, BILL_LOGIN, DOMAIN, YANDEX_SHOP_ID, YANDEX_scid
|
|
from lms.tools import random_string, gen_pay_sig, out_uri, out_date_format
|
|
from courses.models import Course, MATERIALS_TYPE, CourseMap
|
|
from management.letters import sent_created_my_self_bill, sent_new_my_self_bill, new_student, \
|
|
pay_no_public_course, sent_new_service_request_to_out
|
|
from service.models import MailBox
|
|
|
|
from courses.models import Flow
|
|
|
|
|
|
class Installment(models.Model):
|
|
initiative = models.ForeignKey('Price', verbose_name=u'Иниациатива')
|
|
user = models.ForeignKey(User, verbose_name=u'Пользователь')
|
|
price = models.IntegerField(verbose_name=u'Общая цена')
|
|
first_price = models.IntegerField(verbose_name=u'Первый платеж', default=0)
|
|
period = models.IntegerField(verbose_name=u'Месяцев на оплату', default=5)
|
|
date = models.DateTimeField(verbose_name=u'Дата открытия', default=datetime.datetime.now, editable=False)
|
|
f_date = models.DateField(verbose_name=u'Дата закрытия', blank=True)
|
|
rest = models.IntegerField(verbose_name=u'Остаток по задолжности', default=0)
|
|
wait = models.IntegerField(verbose_name=u'Дней в задолжности', default=0, help_text=u'Через сколько дней '
|
|
u'предоставлять доступ после '
|
|
u'просрочки')
|
|
expired = models.BooleanField(verbose_name=u'Просрочено', default=False)
|
|
payments = models.ManyToManyField('Bill', verbose_name=u'Платежи', blank=True, null=True, related_name='bill_point')
|
|
|
|
def __str__(self):
|
|
return '%s %s' % (self.user, self.price)
|
|
|
|
def __unicode__(self):
|
|
return u'%s %s' % (self.user, self.price)
|
|
|
|
class Meta:
|
|
verbose_name = u'Рассрочка'
|
|
verbose_name_plural = u'Рассрочки'
|
|
|
|
|
|
class Price(models.Model):
|
|
# Цены
|
|
freepay = models.BooleanField(verbose_name=u'Свободный счет', default=False, editable=False)
|
|
m_type = models.CharField(verbose_name=u'Тип подписки', max_length=1, choices=MATERIALS_TYPE, default='B')
|
|
key = models.CharField(verbose_name=u'Ключ доступа', max_length=255, blank=True, editable=False, null=True)
|
|
url = models.URLField(verbose_name=u'Ссылка на оплату', blank=True, null=True, editable=False)
|
|
public = models.BooleanField(verbose_name=u'Опубликовать', default=False)
|
|
title = models.CharField(verbose_name=u'Услуга', max_length=255, help_text=u'Будет показано пользователям')
|
|
cost = models.IntegerField(verbose_name=u'Цена')
|
|
course = models.ForeignKey(Course, verbose_name=u'Курс', null=True, blank=True)
|
|
description = models.TextField(verbose_name=u'Описание', help_text=u'Будет показано менеджерам')
|
|
included = models.ManyToManyField(CourseMap, verbose_name=u'Включены', null=True, blank=True,
|
|
help_text=u'Если задействовать эту функцию, стандартная схема подписок будет '
|
|
u'не активна')
|
|
by_time = models.IntegerField(verbose_name=u'Дней доступа', default=0, blank=True, null=True,
|
|
help_text=u'По истечении этого периода доступ будет закрыт для включений.')
|
|
post_fire = models.IntegerField(verbose_name=u'Дней сгорания', default=0,
|
|
help_text=u'Предоставлении 50% скиндки на X дней. Сработает только при наличии "дней доступа"')
|
|
|
|
def get_name(self):
|
|
return '{1} / {0}'.format(self.course.get_title(), self.title) if self.course else self.title
|
|
|
|
def __str__(self):
|
|
return '%s / %s %s' % (self.course, self.title, self.cost)
|
|
|
|
def __unicode__(self):
|
|
return u'%s / %s %s' % (self.course, self.title, self.cost)
|
|
|
|
def check_points(self):
|
|
# Проверка существования всех точек курса в услуге
|
|
results = 0
|
|
for point in CourseMap.objects.filter(course=self.course):
|
|
if not self.included.filter(id=point.id).exists():
|
|
self.included.add(point)
|
|
results += 1
|
|
return results
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.key:
|
|
self.key = random_string(length=50)
|
|
|
|
if self.key and not self.url:
|
|
self.url = DOMAIN + '/wallet/pay/self_bill/' + self.key
|
|
|
|
if not self.course and not self.freepay:
|
|
self.freepay = True
|
|
|
|
elif self.course and self.freepay:
|
|
self.freepay = False
|
|
|
|
super(Price, self).save(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = u'Услуга'
|
|
verbose_name_plural = u'Услуги'
|
|
ordering = ['-id']
|
|
|
|
|
|
class Bill(models.Model):
|
|
BILL_STATUSES = (
|
|
('W', u'Ожидание согласия'),
|
|
('P', u'На оплате'),
|
|
('F', u'Оплачен'),
|
|
('C', u'Отклонен'),
|
|
('B', u'Отклонен банком'),
|
|
('H', u'Сгорел')
|
|
)
|
|
BILL_METHOD = (
|
|
('C', u'Наличные'),
|
|
('H', u'JustClick'),
|
|
('B', u'Банковский перевод'),
|
|
('G', u'На расчетный счет'),
|
|
('A', u'Альфа-Банк'),
|
|
('P', u'PayPal'),
|
|
('W', u'WebMoney'),
|
|
('S', u'SimplePay'),
|
|
('Y', u'YandexKassa')
|
|
)
|
|
gift = models.BooleanField(verbose_name=u'Подарок', default=False)
|
|
status = models.CharField(verbose_name=u'Статус', max_length=1, default='W', choices=BILL_STATUSES)
|
|
_method = models.CharField(verbose_name=u'Способ оплаты', max_length=2, default='Y', choices=BILL_METHOD)
|
|
price = models.CharField(verbose_name=u'Сумма', max_length=255, null=True, blank=True)
|
|
real_price = models.CharField(verbose_name=u'Полученная сумма', max_length=255, null=True, blank=True,
|
|
help_text=u'Сумма, минус комиссия')
|
|
traf_source = models.ForeignKey(TrafHistory, verbose_name=u'Обращение', blank=True, null=True, editable=False)
|
|
service = models.ForeignKey(Price, verbose_name=u'Оплачиваемая услуга')
|
|
flow = models.ForeignKey(
|
|
Flow, verbose_name=u'Поток', blank=True, null=True)
|
|
inside_data = models.TextField(verbose_name=u'Данные проверки', default='', blank=True, editable=False)
|
|
#
|
|
user = models.ForeignKey(User, verbose_name=u'Плательщик', related_name=u'bill_user')
|
|
opener = models.ForeignKey(User, verbose_name=u'Открывший', related_name=u'bill_opener', null=True)
|
|
manager = models.ForeignKey(User, verbose_name=u'Менеджер', related_name=u'bill_manager', editable=False, null=True)
|
|
#
|
|
source_key = models.CharField(verbose_name=u'Исходник ключа', blank=True, max_length=255, default='', editable=False)
|
|
key = models.CharField(verbose_name=u'Ключ платежа', blank=True, max_length=255, default='', editable=False)
|
|
out_id = models.CharField(verbose_name=u'ID внешнего заказа', max_length=100, blank=True, default='', editable=False)
|
|
admitad_uid = models.CharField(verbose_name=u'Ключь admitad', max_length=100, blank=True, default='', editable=False)
|
|
date = models.DateTimeField(verbose_name=u'Дата создания счета', default=django.utils.timezone.now, editable=False)
|
|
status_changed = models.DateTimeField(verbose_name=u'Дата смены статуса', default=django.utils.timezone.now, editable=False)
|
|
comment = models.TextField(verbose_name=u'Комментарий продавца', help_text=u'Будет показано пользователю',
|
|
blank=True, editable=False)
|
|
description = models.TextField(verbose_name=u'Внутренняя заметка', help_text=u'Показано только внутри', default='',
|
|
blank=True)
|
|
finish_date = models.DateTimeField(verbose_name=u'Дата завершения', blank=True, null=True, editable=False)
|
|
salt = models.CharField(verbose_name=u'Соль', max_length=100, blank=True, default=random_string, editable=False)
|
|
views = models.IntegerField(verbose_name=u'Показать напоминание', default=0, help_text=u'Количество показов '
|
|
u'напоминания', blank=True)
|
|
start_fire = models.DateTimeField(verbose_name=u'Дата начала сгорания', blank=True, null=True)
|
|
start_fire_sent = models.BooleanField(verbose_name=u'Письмо сгорания', default=False, editable=False)
|
|
fire_date = models.DateTimeField(verbose_name=u'Дата сгорания', blank=True, null=True)
|
|
fire_date_sent = models.BooleanField(verbose_name=u'Письмо сгорания', default=False, editable=False)
|
|
modals_show = models.BooleanField(verbose_name=u'Модалки показаны', default=False, editable=False)
|
|
created_sent = models.BooleanField(verbose_name=u'Отправлено письмо сформированного счета', default=False, editable=False)
|
|
create_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма при создании', null=True, blank=True, related_name='bill_create_letter', editable=False)
|
|
finish_letters = models.ManyToManyField(MailBox, verbose_name=u'Письма завершения', null=True, blank=True, related_name='bill_finish_letter', editable=False)
|
|
|
|
def __unicode__(self):
|
|
return u'%s:%s %s %s' % (self.id, self.get_status_display(), self.user, self.manager if self.manager else '')
|
|
|
|
def __str__(self):
|
|
return '%s:%s %s %s' % (self.id, self.get_status_display(), self.user, self.manager if self.manager else '')
|
|
|
|
def get_face(self):
|
|
return {
|
|
'id': self.id,
|
|
'price': self.price,
|
|
'status': {'flag': self.status, 'title': self.get_status_display()},
|
|
'admitad_uid': self.admitad_uid,
|
|
'pay_url': '{0}/wallet/bill_out/{1}'.format(DOMAIN, self.salt),
|
|
'service': self.service.key,
|
|
'gift': self.gift,
|
|
'title': self.get_name(),
|
|
'user': '<span style="background: #333;color: #fff;font-weight: bold;padding: 3px 10px;border: 2px solid #ff1;">{0} / {1} / {2}</span>'.format(self.user.get_full_name(), self.user.get_phone(), self.user.email),
|
|
'manager': self.manager.get_short_name() if self.manager else ''
|
|
}
|
|
|
|
def get_name(self):
|
|
if self.service:
|
|
return self.service.get_name()
|
|
else:
|
|
return 'Свободный счет ID:{0}'.format(self.id)
|
|
|
|
# TODO: Что это вообще такое? Это далеко не статус
|
|
def get_status_flag(self):
|
|
if not self.service.by_time and not self.service.included.exists():
|
|
return self.service.m_type
|
|
|
|
elif self.service.by_time:
|
|
return 'S'
|
|
|
|
elif self.service.included.exists():
|
|
return 'F'
|
|
|
|
def get_type(self):
|
|
if self.installment:
|
|
return 'I'
|
|
elif self.freeprice:
|
|
return 'F'
|
|
elif self.service:
|
|
return 'S'
|
|
|
|
def get_pay_parameters(self):
|
|
# Получение параметров для создания подписи
|
|
parameters = {
|
|
'sp_amount': self.price,
|
|
'sp_description': '{0} {1} Заказ: ID{2} Менеджер: {3}'.format(self.get_name(), self.user.full_data(), self.id, self.manager.get_short_name() if self.manager else ''),
|
|
'sp_order_id': self.id,
|
|
'sp_outlet_id': BILL_LOGIN,
|
|
'sp_salt': self.salt,
|
|
'sp_user_name': self.user.get_full_name(),
|
|
'sp_user_phone': self.user.get_phone(),
|
|
'sp_user_contact_email': self.user.email,
|
|
'sp_user_ip': self.user.get_ip(), # IP пользователя, совершающего платеж
|
|
'sp_user_params': '', # Параметры, передаваемые на ResultURL по этому запросу,
|
|
}
|
|
result = {}
|
|
for key, value in parameters.items():
|
|
if value: result[key] = value
|
|
|
|
return result
|
|
|
|
def get_comment(self):
|
|
return '{0} {1} Заказ: ID{2} Менеджер: {3}'.format(self.get_name(), self.user.full_data(), self.id, self.manager.get_short_name() if self.manager else '')
|
|
|
|
def gen_robokassa_url(self):
|
|
_params = self.get_pay_parameters()
|
|
_params['sp_sig'] = gen_pay_sig(_params, method='payment', result=False)
|
|
|
|
#for key, value in _params.items():
|
|
# if value: result += '%s=%s&' % (key, value)
|
|
|
|
return out_uri(BILL_URL, _params)
|
|
|
|
def gen_yandex_data(self):
|
|
return {'url': 'https://money.yandex.ru/eshop.xml', 'data': {'shopId': YANDEX_SHOP_ID,
|
|
'scid': YANDEX_scid,
|
|
'orderNumber': self.id,
|
|
'customerNumber': self.user.id,
|
|
'sum': self.price}}
|
|
|
|
def gen_pay_link(self):
|
|
return '{0}/wallet/bill_out/{1}'.format(DOMAIN, self.salt)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if self.price == '0' or self.gift:
|
|
self.finish_date = datetime.datetime.now()
|
|
self.status_changed = datetime.datetime.now()
|
|
self.gift = True
|
|
self.status = 'F'
|
|
|
|
else:
|
|
if not self.comment:
|
|
self.comment = self.get_comment()
|
|
|
|
if not self.salt:
|
|
self.salt = random_string()
|
|
|
|
if not self.price:
|
|
self.price = self.service.cost
|
|
|
|
if (self.service.by_time and self.finish_date) and self.service.post_fire and not self.fire_date:
|
|
self.fire_date = self.finish_date + datetime.timedelta(days=(self.service.by_time+self.service.post_fire))
|
|
|
|
if (self.service.by_time and self.finish_date) and not self.start_fire:
|
|
self.start_fire = self.finish_date + datetime.timedelta(days=self.service.by_time)
|
|
|
|
if self.fire_date or self.views:
|
|
self._type = 'P'
|
|
|
|
if not self.opener:
|
|
if self.status == 'W' and self.price != 0 and not self.created_sent:
|
|
if self.manager and self.manager.in_role in ['M', 'S'] and not self.traf_source:
|
|
for supervisor in User.objects.filter(in_role='S'):
|
|
sent_new_my_self_bill(self, supervisor.email)
|
|
self.created_sent = True
|
|
|
|
if (self.status == 'F' or self.status == 'C') and not self.finish_date:
|
|
self.finish_date = django.utils.timezone.now()
|
|
if self.status == 'F' and not self.gift:
|
|
self.user.customer = True
|
|
self.user.save()
|
|
# Отправить письма куратору
|
|
if self.service and self.service.course:
|
|
if self.service.course.public:
|
|
for mentor in self.service.course.get_mentors():
|
|
new_student(self, mentor.email)
|
|
else:
|
|
for mentor in self.service.course.get_mentors():
|
|
pay_no_public_course(self, mentor.email)
|
|
|
|
if self.manager and self.manager.in_role in ['M', 'S'] and not self.traf_source:
|
|
title = 'Оплата отклонена' if self.status == 'C' else 'Счет оплачен'
|
|
sent_created_my_self_bill(self, self.manager.email, title)
|
|
for supervisor in User.objects.filter(in_role='S'):
|
|
sent_created_my_self_bill(self, supervisor.email, title)
|
|
|
|
super(Bill, self).save(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = u'Счет'
|
|
verbose_name_plural = u'Счета'
|
|
|
|
|
|
class ServiceRequest(models.Model):
|
|
SERVICE_REQUEST_STATUS = (
|
|
('S', u'Не обработан'),
|
|
('E', u'Ошибка контекста'),
|
|
('W', u'В работе'),
|
|
('F', u'Продан'),
|
|
('B', u'Не продал')
|
|
)
|
|
BY_TYPE = (
|
|
('S', 'Органично'),
|
|
('B', 'Привлечен')
|
|
)
|
|
company = models.CharField(verbose_name=u'Компания продавец', max_length=255, blank=True, default='')
|
|
product = models.CharField(verbose_name=u'Продукт', max_length=255, blank=True, default='')
|
|
service = models.CharField(verbose_name=u'Услуга', max_length=255, blank=True, default='')
|
|
name = models.CharField(verbose_name=u'Имя', max_length=255, blank=True, default='')
|
|
email = models.CharField(verbose_name=u'email', max_length=255, blank=True, default='')
|
|
phone = models.CharField(verbose_name=u'phone', max_length=255, blank=True, default='')
|
|
lead_name = models.CharField(verbose_name=u'Метка amo', max_length=255, blank=True, default='')
|
|
data = models.TextField(verbose_name=u'Данные', help_text=u'Словарь упаковывается в строку', default='')
|
|
host = models.CharField(verbose_name=u'Источник запроса', default='', editable=False, max_length=255)
|
|
_type = models.CharField(verbose_name=u'Тип запроса', max_length=1, choices=BY_TYPE, default='S')
|
|
status = models.CharField(verbose_name=u'Статус', max_length=1, choices=SERVICE_REQUEST_STATUS, default='S')
|
|
student = models.ForeignKey(User, verbose_name=u'Студент', related_name='student_user', blank=True, null=True)
|
|
course = models.ForeignKey(Course, verbose_name=u'Курс', null=True)
|
|
manager = models.ForeignKey(User, verbose_name=u'Продавец', related_name='manager_user', blank=True, null=True)
|
|
cancel_description = models.TextField(verbose_name=u'Причина отказа', default='', blank=True)
|
|
charge = models.TextField(verbose_name=u'Поручение', default='', blank=True)
|
|
date = models.DateTimeField(verbose_name=u'Дата заказа', default=datetime.datetime.now)
|
|
f_date = models.DateTimeField(verbose_name=u'Дата обаботки', blank=True, null=True)
|
|
send = models.BooleanField(verbose_name=u'Отправлен в AMO', default=False, editable=False)
|
|
send_date = models.DateTimeField(verbose_name=u'Время отправки', default=datetime.datetime.now, editable=False)
|
|
|
|
def get_name(self):
|
|
if self.course:
|
|
return u'Доступ к курсу "%s"' % self.course.get_title()
|
|
else:
|
|
return u'Счет: ID%s' % self.id
|
|
|
|
def get_client_name(self):
|
|
_name = ''
|
|
if self.student:
|
|
_name = self.student.get_full_name()
|
|
elif self.name:
|
|
_name = self.name
|
|
|
|
return u'%s' % _name
|
|
|
|
def get_lead_name(self):
|
|
if self.lead_name:
|
|
_name = self.lead_name
|
|
|
|
else:
|
|
_product = ''
|
|
if self.product:
|
|
_product = self.product
|
|
|
|
elif self.course:
|
|
_product = self.course.get_title()
|
|
|
|
_name = u'%s | %s | %s' % (self.company if self.company else 'LMS',
|
|
_product,
|
|
self.service if self.service else 'Заявка на покупку')
|
|
return u'%s' % _name
|
|
|
|
def sent_to_amo(self):
|
|
if self.data:
|
|
try:
|
|
data = json.loads(self.data.replace("'", "\""))
|
|
except JSONDecodeError:
|
|
self.status = "E"
|
|
|
|
else:
|
|
name = ''
|
|
phone = ''
|
|
email = ''
|
|
|
|
if self.name:
|
|
name = self.name
|
|
elif self.student:
|
|
name = self.student.get_full_name()
|
|
|
|
if self.phone:
|
|
phone = self.phone
|
|
elif self.student:
|
|
phone = self.student.get_phone()
|
|
|
|
if self.email:
|
|
email = self.email
|
|
elif self.student:
|
|
email = self.student.email
|
|
|
|
data = {
|
|
'name': name,
|
|
'phone': phone,
|
|
'email': email,
|
|
'lead_name': self.get_lead_name()
|
|
}
|
|
|
|
if data['name'] == '':
|
|
data['name'] = 'empty'
|
|
|
|
urllib.request.urlopen(out_uri('https://skill-box.ru/amocrm/CreateLead.php', data))
|
|
if not self.send and self.status != 'E':
|
|
data['host'] = self.host
|
|
self.send = True
|
|
self.save()
|
|
self.send_alert(data)
|
|
|
|
def __unicode__(self):
|
|
return u'%s %s' % (self.status, self.student)
|
|
|
|
def __str__(self):
|
|
return u'%s %s' % (self.status, self.student)
|
|
|
|
def send_alert(self, data):
|
|
# Отправка оповещений
|
|
# Получить почты
|
|
try:
|
|
r = RequestAlert.objects.get(title=self.lead_name)
|
|
except RequestAlert.DoesNotExists:
|
|
pass
|
|
else:
|
|
# Отправить письма
|
|
for i in r.get_mails():
|
|
sent_new_service_request_to_out(self.lead_name, data, i)
|
|
|
|
def save(self, *args, **kwargs):
|
|
super(ServiceRequest, self).save(*args, **kwargs)
|
|
|
|
class Meta:
|
|
verbose_name = u'Запрос на покупку'
|
|
verbose_name_plural = u'Запросы на прокупки'
|
|
|
|
|
|
class RequestAlert(models.Model):
|
|
title = models.CharField(verbose_name=u'Классификатор', max_length=255, editable=False)
|
|
requests = models.ManyToManyField(ServiceRequest, verbose_name=u'Количество заявок', blank=True, null=True, editable=False)
|
|
mails = models.TextField(verbose_name=u'Список почт', blank=True,
|
|
help_text=u'Кому разослать дубли заявки. ЧЕРЕЗ ТОЧКУ С ЗАПЯТОЙ')
|
|
date = models.DateTimeField(verbose_name=u'Начало заявок', default=datetime.datetime.now, editable=False)
|
|
f_date = models.DateTimeField(verbose_name=u'Последняя заявка', default=datetime.datetime.now, editable=False)
|
|
|
|
def __unicode__(self):
|
|
return u'%s' % self.title
|
|
|
|
def __str__(self):
|
|
return u'%s' % self.title
|
|
|
|
def _count(self):
|
|
return self.requests.filter(date__gte=self.date).count()
|
|
|
|
def up_count(self, request):
|
|
# Увеличение счетчика
|
|
self.requests.add(request)
|
|
self.f_date = datetime.datetime.now()
|
|
self.save()
|
|
|
|
def get_mails(self):
|
|
return map(lambda x: x.replace(" ", ""), str(self.mails).split(';')) if self.mails else []
|
|
|
|
def get_mails_str(self):
|
|
return str(self.mails)
|
|
|
|
class Meta:
|
|
verbose_name = u'Оповещение о новых заявках'
|
|
verbose_name_plural = u'Оповещения о новых заявках'
|
|
|
|
|
|
class YandexKassaHistory(models.Model):
|
|
_type = models.CharField(verbose_name=u'Тип запроса', max_length=255, default='')
|
|
date = models.DateTimeField(verbose_name=u'Дата', default=datetime.datetime.now)
|
|
text = models.TextField(verbose_name=u'Текст')
|
|
result = models.CharField(verbose_name=u'Результат', max_length=255, default='')
|
|
|
|
def __unicode__(self):
|
|
return u'%s' % out_date_format(self.date)
|
|
|
|
def __str__(self):
|
|
return str(out_date_format(self.date))
|
|
|
|
class Meta:
|
|
verbose_name = u'Яндекс Касса'
|
|
verbose_name_plural = u'Яндекс Касса'
|
|
|