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.
651 lines
20 KiB
651 lines
20 KiB
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from accounts_app.models import UserProfile, MyUserManager, BaseAccount
|
|
from bitfield import BitField
|
|
from django.conf import settings
|
|
from django.core import validators
|
|
from django.core.validators import RegexValidator
|
|
from django.db import models, connection, transaction
|
|
from django.db.models.signals import post_delete, pre_delete, post_init, \
|
|
pre_save
|
|
from django.dispatch import receiver
|
|
from django.shortcuts import resolve_url
|
|
from django.utils import timezone
|
|
from django.utils.translation import ugettext_lazy as _, gettext
|
|
from djing.lib import LogicError
|
|
from group_app.models import Group
|
|
from gw_app.nas_managers import SubnetQueue, NasFailedResult, NasNetworkError
|
|
from ip_pool.models import NetworkModel
|
|
from tariff_app.models import Tariff, PeriodicPay
|
|
|
|
|
|
class AbonLog(models.Model):
|
|
abon = models.ForeignKey('Abon', on_delete=models.CASCADE)
|
|
amount = models.FloatField(default=0.0)
|
|
author = models.ForeignKey(UserProfile, on_delete=models.SET_NULL,
|
|
related_name='+', blank=True, null=True)
|
|
comment = models.CharField(max_length=128)
|
|
date = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'abonent_log'
|
|
ordering = '-date',
|
|
|
|
def __str__(self):
|
|
return self.comment
|
|
|
|
|
|
class AbonTariff(models.Model):
|
|
tariff = models.ForeignKey(
|
|
Tariff,
|
|
on_delete=models.CASCADE,
|
|
related_name='linkto_tariff'
|
|
)
|
|
|
|
time_start = models.DateTimeField(null=True, blank=True, default=None)
|
|
|
|
deadline = models.DateTimeField(null=True, blank=True, default=None)
|
|
|
|
def calc_amount_service(self):
|
|
amount = self.tariff.amount
|
|
return round(amount, 2)
|
|
|
|
def __str__(self):
|
|
return "%s: %s" % (
|
|
self.deadline,
|
|
self.tariff.title
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'abonent_tariff'
|
|
permissions = (
|
|
('can_complete_service', _('finish service perm')),
|
|
)
|
|
verbose_name = _('Abon service')
|
|
verbose_name_plural = _('Abon services')
|
|
ordering = ('time_start',)
|
|
|
|
|
|
class AbonStreet(models.Model):
|
|
name = models.CharField(max_length=64)
|
|
group = models.ForeignKey(Group, on_delete=models.CASCADE)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Meta:
|
|
db_table = 'abon_street'
|
|
verbose_name = _('Street')
|
|
verbose_name_plural = _('Streets')
|
|
ordering = 'name',
|
|
|
|
|
|
class AbonManager(MyUserManager):
|
|
def get_queryset(self):
|
|
return super(MyUserManager, self).get_queryset().filter(is_admin=False)
|
|
|
|
|
|
class Abon(BaseAccount):
|
|
current_tariff = models.OneToOneField(
|
|
AbonTariff,
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
default=None
|
|
)
|
|
group = models.ForeignKey(
|
|
Group,
|
|
on_delete=models.SET_NULL,
|
|
blank=True, null=True,
|
|
verbose_name=_('User group')
|
|
)
|
|
ballance = models.FloatField(default=0.0)
|
|
ip_address = models.GenericIPAddressField(
|
|
verbose_name=_('Ip address'),
|
|
null=True,
|
|
blank=True
|
|
)
|
|
description = models.TextField(
|
|
_('Comment'),
|
|
null=True,
|
|
blank=True
|
|
)
|
|
street = models.ForeignKey(
|
|
AbonStreet,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
verbose_name=_('Street')
|
|
)
|
|
house = models.CharField(
|
|
_('House'),
|
|
max_length=12,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
device = models.ForeignKey(
|
|
'devapp.Device',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL
|
|
)
|
|
dev_port = models.ForeignKey(
|
|
'devapp.Port',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL
|
|
)
|
|
is_dynamic_ip = models.BooleanField(
|
|
_('Is dynamic ip'),
|
|
default=False
|
|
)
|
|
nas = models.ForeignKey(
|
|
'gw_app.NASModel',
|
|
null=True,
|
|
blank=True,
|
|
on_delete=models.SET_NULL,
|
|
verbose_name=_('Network access server'),
|
|
default=None
|
|
)
|
|
autoconnect_service = models.BooleanField(
|
|
_('Automatically connect next service'),
|
|
default=False
|
|
)
|
|
last_connected_tariff = models.ForeignKey(
|
|
Tariff, verbose_name=_('Last connected service'),
|
|
on_delete=models.SET_NULL, null=True, blank=True, default=None
|
|
)
|
|
|
|
MARKER_FLAGS = (
|
|
('icon_donkey', _('Donkey')),
|
|
('icon_fire', _('Fire')),
|
|
('icon_ok', _('Ok')),
|
|
('icon_king', _('King')),
|
|
('icon_tv', _('TV')),
|
|
('icon_smile', _('Smile')),
|
|
('icon_dollar', _('Dollar')),
|
|
('icon_service', _('Service')),
|
|
('icon_mrk', _('Marker'))
|
|
)
|
|
markers = BitField(flags=MARKER_FLAGS, default=0)
|
|
|
|
def get_flag_icons(self):
|
|
"""
|
|
Return icon list of set flags from self.markers
|
|
:return: ['m-icon-donkey', 'm-icon-tv', ...]
|
|
"""
|
|
return tuple("m-%s" % name for name, state in self.markers if state)
|
|
|
|
def is_markers_empty(self):
|
|
return int(self.markers) == 0
|
|
|
|
def active_tariff(self):
|
|
return self.current_tariff
|
|
|
|
objects = AbonManager()
|
|
|
|
class Meta:
|
|
db_table = 'abonent'
|
|
permissions = (
|
|
('can_buy_tariff', _('Buy service perm')),
|
|
('can_add_ballance', _('fill account')),
|
|
('can_ping', _('Can ping'))
|
|
)
|
|
verbose_name = _('Abon')
|
|
verbose_name_plural = _('Abons')
|
|
ordering = ('fio',)
|
|
unique_together = ('ip_address', 'nas')
|
|
|
|
def add_ballance(self, current_user, amount, comment):
|
|
AbonLog.objects.create(
|
|
abon=self,
|
|
amount=amount,
|
|
author=current_user if isinstance(current_user,
|
|
UserProfile) else None,
|
|
comment=comment
|
|
)
|
|
self.ballance += amount
|
|
|
|
def pick_tariff(self, tariff, author, comment=None, deadline=None) -> None:
|
|
"""
|
|
Trying to buy a service if enough money.
|
|
:param tariff: instance of tariff_app.models.Tariff.
|
|
:param author: Instance of accounts_app.models.UserProfile.
|
|
Who connected this service. May be None if author is a system.
|
|
:param comment: Optional text for logging this pay.
|
|
:param deadline: Instance of datetime.datetime. Date when service is
|
|
expired.
|
|
:return: Nothing
|
|
"""
|
|
if not isinstance(tariff, Tariff):
|
|
raise TypeError
|
|
|
|
amount = round(tariff.amount, 2)
|
|
|
|
if tariff.is_admin and author is not None:
|
|
if not author.is_staff:
|
|
raise LogicError(
|
|
_('User that is no staff can not buy admin services')
|
|
)
|
|
|
|
if self.current_tariff is not None:
|
|
if self.current_tariff.tariff == tariff:
|
|
# if service already connected
|
|
raise LogicError(_('That service already activated'))
|
|
else:
|
|
# if service is present then speak about it
|
|
raise LogicError(_('Service already activated'))
|
|
|
|
# if not enough money
|
|
if self.ballance < amount:
|
|
raise LogicError(_('%s not enough money for service %s') % (
|
|
self.username, tariff.title
|
|
))
|
|
|
|
with transaction.atomic():
|
|
new_abtar = AbonTariff.objects.create(
|
|
deadline=deadline, tariff=tariff
|
|
)
|
|
self.current_tariff = new_abtar
|
|
if self.last_connected_tariff != tariff:
|
|
self.last_connected_tariff = tariff
|
|
|
|
# charge for the service
|
|
self.ballance -= amount
|
|
|
|
self.save(update_fields=(
|
|
'ballance',
|
|
'current_tariff',
|
|
'last_connected_tariff'
|
|
))
|
|
|
|
# make log about it
|
|
AbonLog.objects.create(
|
|
abon=self, amount=-tariff.amount,
|
|
author=author,
|
|
comment=comment or _('Buy service default log')
|
|
)
|
|
|
|
def attach_ip_addr(self, ip, strict=False):
|
|
"""
|
|
Attach ip address to account
|
|
:param ip: Instance of str or ip_address
|
|
:param strict: If strict is True then ip not replaced quietly
|
|
:return: None
|
|
"""
|
|
if strict and self.ip_address:
|
|
raise LogicError('Ip address already exists')
|
|
self.ip_address = ip
|
|
self.save(update_fields=('ip_address',))
|
|
|
|
def free_ip_addr(self) -> bool:
|
|
if self.ip_address:
|
|
self.ip_address = None
|
|
self.save(update_fields=('ip_address',))
|
|
return True
|
|
return False
|
|
|
|
# is subscriber have access to service,
|
|
# view in tariff_app.custom_tariffs.<TariffBase>.manage_access()
|
|
def is_access(self) -> bool:
|
|
if not self.is_active:
|
|
return False
|
|
abon_tariff = self.active_tariff()
|
|
if abon_tariff is None:
|
|
return False
|
|
trf = abon_tariff.tariff
|
|
ct = trf.get_calc_type()(abon_tariff)
|
|
return ct.manage_access(self)
|
|
|
|
# make subscriber from agent structure
|
|
def build_agent_struct(self):
|
|
if not self.ip_address:
|
|
return
|
|
abon_tariff = self.active_tariff()
|
|
if abon_tariff:
|
|
abon_tariff = abon_tariff.tariff
|
|
return SubnetQueue(
|
|
name="uid%d" % self.pk,
|
|
network=self.ip_address,
|
|
max_limit=(abon_tariff.speedIn, abon_tariff.speedOut),
|
|
is_access=self.is_access()
|
|
)
|
|
|
|
def nas_sync_self(self) -> Optional[Exception]:
|
|
"""
|
|
Synchronize user with gateway
|
|
:return:
|
|
"""
|
|
if self.nas is None:
|
|
raise LogicError(_('gateway required'))
|
|
try:
|
|
agent_abon = self.build_agent_struct()
|
|
if agent_abon is not None:
|
|
mngr = self.nas.get_nas_manager()
|
|
mngr.update_user(agent_abon)
|
|
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
|
|
print('ERROR:', e)
|
|
return e
|
|
except LogicError:
|
|
pass
|
|
|
|
def nas_add_self(self):
|
|
"""
|
|
Will add this user to network access server
|
|
:return:
|
|
"""
|
|
if self.nas is None:
|
|
raise LogicError(_('gateway required'))
|
|
try:
|
|
agent_abon = self.build_agent_struct()
|
|
if agent_abon is not None:
|
|
mngr = self.nas.get_nas_manager()
|
|
mngr.add_user(agent_abon)
|
|
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
|
|
print('ERROR:', e)
|
|
return e
|
|
except LogicError:
|
|
pass
|
|
|
|
def nas_remove_self(self):
|
|
"""
|
|
Will remove this user to network access server
|
|
:return:
|
|
"""
|
|
if self.nas is None:
|
|
raise LogicError(_('gateway required'))
|
|
try:
|
|
agent_abon = self.build_agent_struct()
|
|
if agent_abon is not None:
|
|
mngr = self.nas.get_nas_manager()
|
|
mngr.remove_user(agent_abon)
|
|
except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
|
|
print('ERROR:', e)
|
|
return e
|
|
except LogicError:
|
|
pass
|
|
|
|
def get_absolute_url(self):
|
|
return resolve_url('abonapp:abon_home', self.group.id, self.username)
|
|
|
|
def enable_service(self, tariff: Tariff, deadline=None, time_start=None):
|
|
"""
|
|
Makes a services for current user, without money
|
|
:param tariff: Instance of service
|
|
:param deadline: Time when service is expired
|
|
:param time_start: Time when service has started
|
|
:return: None
|
|
"""
|
|
if deadline is None:
|
|
deadline = tariff.calc_deadline()
|
|
if time_start is None:
|
|
time_start = datetime.now()
|
|
new_abtar = AbonTariff.objects.create(
|
|
deadline=deadline, tariff=tariff,
|
|
time_start=time_start
|
|
)
|
|
self.current_tariff = new_abtar
|
|
self.last_connected_tariff = tariff
|
|
self.save(update_fields=('current_tariff', 'last_connected_tariff'))
|
|
|
|
|
|
class PassportInfo(models.Model):
|
|
series = models.CharField(
|
|
_('Pasport serial'),
|
|
max_length=4,
|
|
validators=(validators.integer_validator,)
|
|
)
|
|
number = models.CharField(
|
|
_('Pasport number'),
|
|
max_length=6,
|
|
validators=(validators.integer_validator,)
|
|
)
|
|
distributor = models.CharField(
|
|
_('Distributor'),
|
|
max_length=64
|
|
)
|
|
date_of_acceptance = models.DateField(_('Date of acceptance'))
|
|
abon = models.OneToOneField(
|
|
Abon,
|
|
on_delete=models.CASCADE,
|
|
blank=True,
|
|
null=True
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'passport_info'
|
|
verbose_name = _('Passport Info')
|
|
verbose_name_plural = _('Passport Info')
|
|
ordering = ('series',)
|
|
|
|
def __str__(self):
|
|
return "%s %s" % (self.series, self.number)
|
|
|
|
|
|
class InvoiceForPayment(models.Model):
|
|
abon = models.ForeignKey(Abon, on_delete=models.CASCADE)
|
|
status = models.BooleanField(default=False)
|
|
amount = models.FloatField(default=0.0)
|
|
comment = models.CharField(max_length=128)
|
|
date_create = models.DateTimeField(auto_now_add=True)
|
|
date_pay = models.DateTimeField(blank=True, null=True)
|
|
author = models.ForeignKey(
|
|
UserProfile,
|
|
related_name='+',
|
|
on_delete=models.SET_NULL,
|
|
blank=True,
|
|
null=True
|
|
)
|
|
|
|
def __str__(self):
|
|
return "%s -> %.2f" % (self.abon.username, self.amount)
|
|
|
|
def set_ok(self):
|
|
self.status = True
|
|
self.date_pay = timezone.now()
|
|
|
|
def get_prev_invoice(self):
|
|
return self.objects.order
|
|
|
|
class Meta:
|
|
ordering = ('date_create',)
|
|
db_table = 'abonent_inv_pay'
|
|
verbose_name = _('Debt')
|
|
verbose_name_plural = _('Debts')
|
|
|
|
|
|
class AllTimePayLogManager(models.Manager):
|
|
@staticmethod
|
|
def by_days():
|
|
cur = connection.cursor()
|
|
cur.execute(
|
|
'SELECT SUM(summ) AS alsum, '
|
|
"DATE_TRUNC('day', date_add) AS pay_date "
|
|
'FROM all_time_pay_log '
|
|
'GROUP BY 2 ORDER BY 2'
|
|
)
|
|
while True:
|
|
r = cur.fetchone()
|
|
if r is None:
|
|
break
|
|
summ, dat = r
|
|
yield {
|
|
'summ': summ,
|
|
'pay_date': dat
|
|
}
|
|
|
|
|
|
# Log for pay system "AllTime"
|
|
class AllTimePayLog(models.Model):
|
|
abon = models.ForeignKey(
|
|
Abon,
|
|
on_delete=models.SET_DEFAULT,
|
|
blank=True,
|
|
null=True,
|
|
default=None
|
|
)
|
|
pay_id = models.CharField(
|
|
max_length=36,
|
|
unique=True,
|
|
primary_key=True
|
|
)
|
|
date_add = models.DateTimeField(auto_now_add=True)
|
|
summ = models.FloatField(default=0.0)
|
|
trade_point = models.CharField(
|
|
_('Trade point'),
|
|
max_length=20,
|
|
default=None,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
receipt_num = models.BigIntegerField(_('Receipt number'), default=0)
|
|
|
|
objects = AllTimePayLogManager()
|
|
|
|
def __str__(self):
|
|
return self.pay_id
|
|
|
|
class Meta:
|
|
db_table = 'all_time_pay_log'
|
|
ordering = ('-date_add',)
|
|
|
|
|
|
# log for all terminals
|
|
class AllPayLog(models.Model):
|
|
pay_id = models.CharField(max_length=64, primary_key=True)
|
|
date_action = models.DateTimeField(auto_now_add=True)
|
|
summ = models.FloatField(default=0.0)
|
|
pay_system_name = models.CharField(max_length=16)
|
|
|
|
def __str__(self):
|
|
return self.pay_system_name
|
|
|
|
class Meta:
|
|
db_table = 'all_pay_log'
|
|
ordering = ('-date_action',)
|
|
|
|
|
|
class AbonRawPassword(models.Model):
|
|
account = models.OneToOneField(Abon, models.CASCADE, primary_key=True)
|
|
passw_text = models.CharField(max_length=64)
|
|
|
|
def __str__(self):
|
|
return "%s - %s" % (self.account, self.passw_text)
|
|
|
|
class Meta:
|
|
db_table = 'abon_raw_password'
|
|
|
|
|
|
class AdditionalTelephone(models.Model):
|
|
abon = models.ForeignKey(
|
|
Abon,
|
|
on_delete=models.CASCADE,
|
|
related_name='additional_telephones'
|
|
)
|
|
telephone = models.CharField(
|
|
max_length=16,
|
|
verbose_name=_('Telephone'),
|
|
# unique=True,
|
|
validators=(RegexValidator(
|
|
getattr(settings, 'TELEPHONE_REGEXP', r'^(\+[7,8,9,3]\d{10,11})?$')
|
|
),)
|
|
)
|
|
owner_name = models.CharField(max_length=127)
|
|
|
|
def __str__(self):
|
|
return "%s - (%s)" % (self.owner_name, self.telephone)
|
|
|
|
class Meta:
|
|
db_table = 'additional_telephones'
|
|
ordering = ('owner_name',)
|
|
verbose_name = _('Additional telephone')
|
|
verbose_name_plural = _('Additional telephones')
|
|
|
|
|
|
class PeriodicPayForId(models.Model):
|
|
periodic_pay = models.ForeignKey(
|
|
PeriodicPay,
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_('Periodic pay')
|
|
)
|
|
last_pay = models.DateTimeField(_('Last pay time'), blank=True, null=True)
|
|
next_pay = models.DateTimeField(_('Next time to pay'))
|
|
account = models.ForeignKey(
|
|
Abon,
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_('Account')
|
|
)
|
|
|
|
def payment_for_service(self, author: UserProfile = None, now=None):
|
|
"""
|
|
Charge for the service and leave a log about it
|
|
:param now: Current date, if now is None than it calculates in here
|
|
:param author: instance of UserProfile
|
|
"""
|
|
if now is None:
|
|
now = timezone.now()
|
|
if self.next_pay < now:
|
|
pp = self.periodic_pay
|
|
amount = pp.calc_amount()
|
|
next_pay_date = pp.get_next_time_to_pay(self.last_pay)
|
|
abon = self.account
|
|
with transaction.atomic():
|
|
abon.add_ballance(author, -amount, comment=gettext(
|
|
'Charge for "%(service)s"') % {
|
|
'service': self.periodic_pay
|
|
})
|
|
abon.save(update_fields=('ballance',))
|
|
self.last_pay = now
|
|
self.next_pay = next_pay_date
|
|
self.save(update_fields=('last_pay', 'next_pay'))
|
|
|
|
def __str__(self):
|
|
return "%s %s" % (self.periodic_pay, self.next_pay)
|
|
|
|
class Meta:
|
|
db_table = 'periodic_pay_for_id'
|
|
ordering = ('last_pay',)
|
|
|
|
|
|
@receiver(post_delete, sender=Abon)
|
|
def abon_del_signal(sender, **kwargs):
|
|
abon = kwargs.get("instance")
|
|
if abon is None:
|
|
raise ValueError('Instance does not passed to a signal')
|
|
try:
|
|
abon.nas_remove_self()
|
|
except (NasFailedResult, NasNetworkError, LogicError):
|
|
return True
|
|
|
|
|
|
@receiver(post_init, sender=AbonTariff)
|
|
def abon_tariff_post_init(sender, **kwargs):
|
|
abon_tariff = kwargs["instance"]
|
|
if getattr(abon_tariff, 'time_start') is None:
|
|
abon_tariff.time_start = timezone.now()
|
|
if getattr(abon_tariff, 'deadline') is None:
|
|
calc_obj = abon_tariff.tariff.get_calc_type()(abon_tariff)
|
|
abon_tariff.deadline = calc_obj.calc_deadline()
|
|
|
|
|
|
@receiver(pre_save, sender=AbonTariff)
|
|
def abon_tariff_pre_save(sender, **kwargs):
|
|
abon_tariff = kwargs["instance"]
|
|
if getattr(abon_tariff, 'deadline') is None:
|
|
calc_obj = abon_tariff.tariff.get_calc_type()(abon_tariff)
|
|
abon_tariff.deadline = calc_obj.calc_deadline()
|
|
|
|
|
|
@receiver(pre_delete, sender=AbonTariff)
|
|
def abontariff_pre_delete(sender, **kwargs):
|
|
abon_tariff = kwargs.get("instance")
|
|
if abon_tariff is None:
|
|
raise ValueError('Instance does not passed to a signal')
|
|
try:
|
|
abon = Abon.objects.get(current_tariff=abon_tariff)
|
|
abon.nas_remove_self()
|
|
except (NasFailedResult, NasNetworkError, LogicError):
|
|
return True
|
|
except Abon.DoesNotExist:
|
|
print('Error: abontariff_pre_delete - user not found')
|