Dmitry 9 years ago
parent
commit
e851983552
  1. 20
      abonapp/migrations/0009_abontariff_death_line.py
  2. 66
      abonapp/models.py
  3. 3
      abonapp/urls_abon.py
  4. 11
      abonapp/views.py
  5. 2
      clientsideapp/views.py
  6. 109
      cron.py
  7. 21
      mydefs.py
  8. 6
      tariff_app/base_intr.py
  9. 34
      tariff_app/custom_tariffs.py
  10. 2
      tariff_app/models.py
  11. 4
      templates/abonapp/activate_service.html
  12. 2
      templates/abonapp/buy_tariff.html
  13. 4
      templates/abonapp/services.html

20
abonapp/migrations/0009_abontariff_death_line.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-02-16 12:22
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('abonapp', '0008_auto_20170209_0002'),
]
operations = [
migrations.AddField(
model_name='abontariff',
name='deadline',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]

66
abonapp/models.py

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from django.utils import timezone
from django.utils.datetime_safe import datetime
from django.db import models
from django.core.validators import DecimalValidator
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult
@ -10,14 +9,7 @@ from accounts_app.models import UserProfile
class LogicError(Exception):
def __init__(self, value):
self.message = value
def __str__(self):
return repr(self.message)
def __str__(self):
return repr(self.message)
pass
class AbonGroup(models.Model):
@ -56,6 +48,9 @@ class AbonTariff(models.Model):
# время начала действия, остальные что не начали действие - NULL
time_start = models.DateTimeField(null=True, blank=True, default=None)
# время завершения услуги
deadline = models.DateTimeField(null=True, blank=True, default=None)
def priority_up(self):
# ищем услугу с большим приоритетом(число приоритета меньше)
target_abtar = AbonTariff.objects.filter(
@ -108,21 +103,24 @@ class AbonTariff(models.Model):
# Считает текущую стоимость услуг согласно выбранной для тарифа логики оплаты (см. в документации)
def calc_amount_service(self):
calc_obj = self.tariff.get_calc_type()
calc_obj = self.tariff.get_calc_type()(self)
# calc_obj - instance of tariff_app.custom_tariffs.TariffBase
amount = calc_obj.calc_amount(self)
amount = calc_obj.calc_amount()
return round(amount, 2)
# Активируем тариф
def activate(self, current_user):
calc_obj = self.tariff.get_calc_type()(self)
amnt = self.calc_amount_service()
# если не хватает денег
if self.abon.ballance < amnt:
raise LogicError('Не хватает денег на счету')
# дата активации услуги
# считаем дату активации услуги
self.time_start = timezone.now()
# считаем дату завершения услуги
self.deadline = calc_obj.calc_deadline()
# снимаем деньги за услугу
self.abon.make_pay(current_user, amnt)
self.abon.make_pay(current_user, amnt, u_comment='Завершение и оплата услуги по истечению срока действия')
self.save()
# Используется-ли услуга сейчас, если время старта есть то он активирован
@ -209,7 +207,7 @@ class Abon(UserProfile):
self.ballance += amount
# покупаем тариф
def buy_tariff(self, tariff, author, comment=None):
def pick_tariff(self, tariff, author, comment=None):
assert isinstance(tariff, Tariff)
# выбераем связь ТарифАбонент с самым низким приоритетом
@ -241,47 +239,29 @@ class Abon(UserProfile):
def activate_next_tariff(self, author):
ats = AbonTariff.objects.filter(abon=self).order_by('tariff_priority')
nw = datetime.now(tz=timezone.get_current_timezone())
nw = timezone.datetime.now()
for at in ats:
# Если активированный тариф
if not at.is_started():
return
# время к началу месяца
to_start_month = datetime(nw.year, nw.month, 1, tzinfo=timezone.get_current_timezone())
# проверяем расстояние от Сегодня до начала этого месяца.
# И от заказа тарифа до начала этого месяца
if (nw - at.time_start) > (nw - to_start_month):
# Заказ из прошлого месяца, срок действия закончен
print('Заказ из прошлого месяца, срок действия закончен')
# если услуга просрочена
if nw > at.deadline:
print("Услуга просрочена, отключаем, и подключаем новую")
# выберем следующую по приоритету
# next_tarifs = AbonTariff.objects.filter(tariff_priority__gt = self.tariff_priority, abon=self.abon)
next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2]
next_tarifs = [tr for tr in ats if tr.tariff_priority > at.tariff_priority][:2]
#next_tarifs = filter(lambda tr: tr.tariff_priority > at.tariff_priority, ats)[:2]
# и если что-нибудь вернулось то активируем, давая время начала действия
# и если что-нибудь из списка следующих услуг вернулось - то активируем
if len(next_tarifs) > 0:
next_tarifs[0].time_start = nw
next_tarifs[0].save(update_fields=['time_start'])
next_tarifs[0].activate(author)
# завершаем текущую услугу.
# удаляем запись о текущей услугу.
at.delete()
# Создаём лог о завершении услуги
AbonLog.objects.create(
abon=self,
amount=0,
author=author,
comment='Завершение услуги по истечению срока действия'
)
# есть-ли доступ у абонента к услуге, смотрим в tariff_app.custom_tariffs.<TariffBase>.manage_access()
def is_access(self):
trf = self.active_tariff()
if not trf: return False
ct = trf.get_calc_type()
ct = trf.get_calc_type()()
if ct.manage_access(self):
return True
else:
@ -311,7 +291,7 @@ class InvoiceForPayment(models.Model):
author = models.ForeignKey(UserProfile, related_name='+')
def __str__(self):
return "%s -> %d $" % (self.abon.username, self.amount)
return "%s -> %.2f" % (self.abon.username, self.amount)
def set_ok(self):
self.status = True

3
abonapp/urls_abon.py

@ -1,5 +1,4 @@
from django.conf.urls import url
from . import views
@ -14,7 +13,7 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/pay_history', views.pay_history, name='abon_phistory'),
url(r'^(?P<uid>\d+)/addinvoice$', views.add_invoice, name='add_invoice'),
url(r'^(?P<uid>\d+)/buy$', views.buy_tariff, name='buy_tariff'),
url(r'^(?P<uid>\d+)/pick$', views.pick_tariff, name='pick_tariff'),
url(r'^(?P<uid>\d+)/chpriority$', views.chpriority, name='chpriority_tariff'),
url(r'^(?P<uid>\d+)/complete_service(?P<srvid>\d+)$', views.complete_service, name='compl_srv'),
url(r'^(?P<uid>\d+)/activate_service(?P<srvid>\d+)$', views.activate_service, name='activate_service'),

11
abonapp/views.py

@ -285,6 +285,7 @@ def abonhome(request, gid, uid):
})
@mydefs.require_ssl
def terminal_pay(request):
from .pay_systems import allpay
ret_text = allpay(request)
@ -329,7 +330,7 @@ def add_invoice(request, gid, uid):
@login_required
@permission_required('abonapp.can_buy_tariff')
def buy_tariff(request, gid, uid):
def pick_tariff(request, gid, uid):
frm = None
grp = get_object_or_404(models.AbonGroup, id=gid)
abon = get_object_or_404(models.Abon, id=uid)
@ -338,7 +339,7 @@ def buy_tariff(request, gid, uid):
frm = forms.BuyTariff(request.POST)
if frm.is_valid():
cd = frm.cleaned_data
abon.buy_tariff(cd['tariff'], request.user)
abon.pick_tariff(cd['tariff'], request.user)
#abon.save()
messages.success(request, 'Тариф успешно выбран')
return redirect('abonapp:abon_services', gid=gid, uid=abon.id)
@ -433,7 +434,7 @@ def activate_service(request, gid, uid, srvid):
abtar.activate(request.user)
messages.success(request, 'Услуга активирована')
return redirect('abonapp:abon_home', gid, uid)
return redirect('abonapp:abon_services', gid, uid)
except NasFailedResult as e:
messages.error(request, e)
@ -441,12 +442,14 @@ def activate_service(request, gid, uid, srvid):
messages.warning(request, e)
except models.LogicError as e:
messages.error(request, e)
calc_obj = abtar.tariff.get_calc_type()(abtar)
return render(request, 'abonapp/activate_service.html', {
'abon': abtar.abon,
'abon_group': abtar.abon.group,
'abtar': abtar,
'amount': amount,
'diff': abtar.abon.ballance - amount
'diff': abtar.abon.ballance - amount,
'deadline': calc_obj.calc_deadline()
})

2
clientsideapp/views.py

@ -45,7 +45,7 @@ def buy_service(request, srv_id):
service = get_object_or_404(Tariff, id=srv_id)
current_service = abon.active_tariff()
if request.method == 'POST':
abon.buy_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"'
abon.pick_tariff(service, request.user, 'Покупка тарифного плана через личный кабинет, тариф "%s"'
% service)
messages.success(request, 'Вы подписались на новую услугу. Она встала на очередь подключений. '
'Когда закончится ваша текущая услуга, то включится эта')

109
cron.py

@ -2,14 +2,13 @@
# -*- coding: utf-8 -*-
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from abonapp.models import Abon, LogicError
from agent import Transmitter, NasNetworkError, NasFailedResult
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from abonapp.models import Abon
from agent import Transmitter
def main():
tm = Transmitter()
# получим инфу о записях в NAS
@ -17,45 +16,63 @@ if __name__ == "__main__":
users = Abon.objects.all()
for user in users:
try:
# бдим за услугами абонента: просроченные отключить, заказанные подключить
user.activate_next_tariff(user)
# если нет ip то и нет смысла лезть в NAS
if user.ip_address is None:
continue
# а есть-ли у абонента доступ к услуге
if not user.is_access():
continue
# если нет ip то и нет смысла лезть в NAS
if user.ip_address is None:
continue
# а есть-ли у абонента доступ к услуге
if not user.is_access():
continue
# строим структуру агента
ab = user.build_agent_struct()
if ab is None:
# если не построилась структура агента, значит нет ip
# а если нет ip то и синхронизировать абонента без ip нельзя
continue
# ищем абонента в списке инфы из nas
abons = [queue for queue in queues if queue is not None]
abons = [{'abon': queue.abon, 'mikro_id': queue.sid} for queue in queues if queue.abon.uid == user.pk]
abons_len = len(abons)
if abons_len < 1:
# абонент не найден в nas, добавим
tm.add_user(ab)
continue
elif abons_len > 1:
# удаляем срез из nas, всё кроме 1й записи
tm.remove_user_range(
[mkid['mikro_id'] for mkid in abons[1:]]
)
# один абонент
# сравним совпадает-ли инфа об абоненте в базе и в nas
if ab == abons[0]['abon']:
# если всё совпадает, то менять нечего
continue
else:
# иначе обновляем абонента
tm.update_user(ab, abons[0]['mikro_id'])
# если не активен то приостановим услугу
if user.is_active:
tm.start_user(abons[0]['mikro_id'])
# строим структуру агента
ab = user.build_agent_struct()
if ab is None:
# если не построилась структура агента, значит нет ip
# а если нет ip то и синхронизировать абонента без ip нельзя
continue
# ищем абонента в списке инфы из nas
abons = [queue for queue in queues if queue is not None]
abons = [{'abon': queue.abon, 'mikro_id': queue.sid} for queue in abons if queue.abon.uid == user.pk]
abons_len = len(abons)
if abons_len < 1:
# абонент не найден в nas, добавим
tm.add_user(ab)
continue
elif abons_len > 1:
# удаляем срез из nas, всё кроме 1й записи
tm.remove_user_range(
[mkid['mikro_id'] for mkid in abons[1:]]
)
# один абонент
# сравним совпадает-ли инфа об абоненте в базе и в nas
if ab == abons[0]['abon']:
# если всё совпадает, то менять нечего
continue
else:
tm.pause_user(abons[0]['mikro_id'])
# иначе обновляем абонента
tm.update_user(ab, abons[0]['mikro_id'])
# если не активен то приостановим услугу
if user.is_active:
tm.start_user(abons[0]['mikro_id'])
else:
tm.pause_user(abons[0]['mikro_id'])
except NasNetworkError as er:
print("Error:", er)
except NasFailedResult as er:
print("Error:", er)
except LogicError as er:
print("Notice:", er)
if __name__ == "__main__":
try:
main()
except NasNetworkError as e:
print(e)
except NasFailedResult as e:
print(e)

21
mydefs.py

@ -5,11 +5,12 @@ import socket
import struct
from collections import Iterator
import os
from django.http import HttpResponse, Http404
from functools import wraps
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import redirect
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db import models
from djing.settings import PAGINATION_ITEMS_PER_PAGE
from djing.settings import PAGINATION_ITEMS_PER_PAGE, DEBUG
ip_addr_regex = r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
@ -138,6 +139,7 @@ def order_helper(request):
# Декоратор проверяет аккаунт, чтоб не пускать клиентов в страницы администрации
def only_admins(fn):
@wraps(fn)
def wrapped(request, *args, **kwargs):
if request.user.is_admin:
return fn(request, *args, **kwargs)
@ -180,3 +182,18 @@ class RuTimedelta(timedelta):
ru_days = 'день'
text_date = '%d %s %s' % (self.days, ru_days, text_date)
return text_date
def require_ssl(view):
"""
Decorator that requires an SSL connection. If the current connection is not SSL, we redirect to the SSL version of
the page.
from: https://gist.github.com/ckinsey/9709984
"""
@wraps(view)
def wrapper(request, *args, **kwargs):
if not DEBUG and not request.is_secure():
target_url = "https://" + request.META['HTTP_HOST'] + request.path_info
return HttpResponseRedirect(target_url)
return view(request, *args, **kwargs)
return wrapper

6
tariff_app/base_intr.py

@ -5,12 +5,12 @@ from abc import ABCMeta, abstractmethod
class TariffBase(metaclass=ABCMeta):
@abstractmethod
def calc_amount(self, abon_tariff):
def calc_amount(self):
"""Считает итоговую сумму платежа"""
@abstractmethod
def get_avail_time(self):
"""Возвращает оставшееся время услуги в секундах"""
def calc_deadline(self):
"""Считаем дату, до которой действкет тариф"""
@staticmethod
def description():

34
tariff_app/custom_tariffs.py

@ -1,42 +1,46 @@
# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from datetime import timedelta
from django.utils import timezone
from .base_intr import TariffBase
from calendar import monthrange
# from abonapp import AbonTariff
class TariffDefault(TariffBase):
# Базовый функционал считает стоимость пропорционально использованному времени
def calc_amount(self, abon_tariff):
def __init__(self, abon_tariff):
#assert isinstance(abon_tariff, AbonTariff)
self.abon_tariff = abon_tariff
# Базовый функционал считает стоимость пропорционально использованному времени
def calc_amount(self):
# сейчас
nw = timezone.now()
# сколько прошло с начала действия услуги
# если времени начала нет то это начало действия, использованное время 0
time_diff = nw - abon_tariff.time_start if abon_tariff.time_start else timedelta(0)
time_diff = nw - self.abon_tariff.time_start if self.abon_tariff.time_start else timedelta(0)
# времени в этом месяце
curr_month_time = datetime(nw.year, nw.month if nw.month == 12 else nw.month + 1, 1) - timedelta(days=1)
curr_month_time = timezone.datetime(nw.year, nw.month if nw.month == 12 else nw.month + 1, 1) - timedelta(days=1)
curr_month_time = timedelta(days=curr_month_time.day)
# Сколько это в процентах от всего месяца (k - коеффициент)
k = time_diff.total_seconds() / curr_month_time.total_seconds()
# результат - это полная стоимость тарифа умноженная на k
res = k * abon_tariff.tariff.amount
res = k * self.abon_tariff.tariff.amount
return float(res)
# возвращаем сколько времени осталось до завершения услуги (конца месяца)
def get_avail_time(self):
from calendar import monthrange
# Тут мы расчитываем конец действия услуги, завершение будет в конце месяца
def calc_deadline(self):
nw = timezone.now()
last_day = monthrange(nw.year, nw.month)[1]
last_month_date = datetime(year=nw.year, month=nw.month, day=last_day,
hour=23,minute=59, second=59,tzinfo=nw.tzinfo)
return last_month_date - nw
last_month_date = timezone.datetime(year=nw.year, month=nw.month, day=last_day,
hour=23, minute=59, second=59)
return timezone.make_aware(last_month_date)
@staticmethod
def description():
@ -47,8 +51,8 @@ class TariffDp(TariffDefault):
# в IS снимается вся стоимость тарифа вне зависимости от времени использования
# просто возвращаем всю стоимость тарифа
def calc_amount(self, abon_tariff):
return float(abon_tariff.tariff.amount)
def calc_amount(self):
return float(self.abon_tariff.tariff.amount)
@staticmethod
def description():
@ -56,7 +60,7 @@ class TariffDp(TariffDefault):
class TariffCp(TariffDefault):
def calc_amount(self, abon_tariff):
def calc_amount(self):
return 12.6
@staticmethod

2
tariff_app/models.py

@ -28,7 +28,7 @@ class Tariff(models.Model):
if len(ob) > 0:
res_type = ob[0][1]
assert issubclass(res_type, TariffBase)
return res_type()
return res_type
def __str__(self):
return "%s (%.2f)" % (self.title, self.amount)

4
templates/abonapp/activate_service.html

@ -24,7 +24,9 @@
<p>Вы уверены что хотите активировать абоненту эту услугу?<br>
Обратите внимание что с его счёта <b>снимутся деньги</b> и откроется доступ к ресурсам оплаченной
услуги.<br>
Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.</p>
Стоимость услуги: {{ amount }}руб. На счету {{ abon.ballance }} руб, останется {{ diff }} руб.<br>
Услуга будет действовать до {{ deadline|date:'d F Y, H:i:s' }}
</p>
<button type="submit" class="btn btn-sm btn-primary">
<span class="glyphicon glyphicon-save"></span> Подтвердить

2
templates/abonapp/buy_tariff.html

@ -19,7 +19,7 @@
</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'abonapp:buy_tariff' abon_group.id abon.id %}"
<form role="form" action="{% url 'abonapp:pick_tariff' abon_group.id abon.id %}"
method="post">{% csrf_token %}
<div class="form-group">
<label for="id_tariff">Выбирите тарифф</label>

4
templates/abonapp/services.html

@ -78,7 +78,7 @@
<tr>
<td colspan="7">Нет подключённых абоненту услуг.
{% if perms.abonapp.can_buy_tariff %}
<a href="{% url 'abonapp:buy_tariff' abon_group.id abon.id %}" class="lgtbx">Купить</a>
<a href="{% url 'abonapp:pick_tariff' abon_group.id abon.id %}" class="lgtbx">Купить</a>
{% endif %}
</td>
</tr>
@ -88,7 +88,7 @@
<tfoot>
<tr>
<th colspan="7">
<a href="{% url 'abonapp:buy_tariff' abon_group.id abon.id %}" class="btn btn-sm btn-success">
<a href="{% url 'abonapp:pick_tariff' abon_group.id abon.id %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> Купить услугу
</a>
</th>

Loading…
Cancel
Save