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.
861 lines
33 KiB
861 lines
33 KiB
# -*- coding: utf-8 -*-
|
|
import os
|
|
|
|
import logging
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from PIL import Image
|
|
from django.db.models.signals import post_save
|
|
from django.dispatch import receiver
|
|
from pytils import numeral
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from django.db import models
|
|
from django.db.models import Max
|
|
from django.core.urlresolvers import reverse
|
|
from django.utils import timezone
|
|
from django.utils.deconstruct import deconstructible
|
|
from django.conf import settings
|
|
|
|
from customer import consts, managers, utils
|
|
from customer.utils import create_bonus_license, get_robokassa_url
|
|
from myauth.models import DokUser
|
|
from commons.utils import only_numerics
|
|
from robokassa.signals import result_received, success_page_visited
|
|
|
|
from . import emails
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# куда сохранять загруженные изображения
|
|
PROFILE_IMAGES_UPLOAD_DIR = 'customer/profile/'
|
|
|
|
BOSS_SIGN_IMG_SIZE = (159, 65)
|
|
GLAVBUH_SIGN_IMG_SIZE = (159, 65)
|
|
STAMP_IMG_SIZE = (180, 180)
|
|
LOGO_SIZE = (351, 121)
|
|
|
|
|
|
def get_profile(user):
|
|
"""Возвращает профиль пользователя или None."""
|
|
try:
|
|
return user.profile
|
|
except:
|
|
return None
|
|
|
|
|
|
@deconstructible
|
|
class UploadAndRename(object):
|
|
|
|
def __init__(self, path, filename):
|
|
self.path = path
|
|
self.filename = filename
|
|
|
|
def __call__(self, instance, filename):
|
|
filename = self.filename or filename
|
|
try:
|
|
profile_dir = instance.get_first_user().username
|
|
except:
|
|
profile_dir = 'NoUser'
|
|
return os.path.join(self.path, profile_dir, filename)
|
|
|
|
|
|
class UserProfile(models.Model):
|
|
"""Профиль пользователя."""
|
|
profile_type = models.PositiveSmallIntegerField('Тип профиля', choices=consts.PROFILE_TYPES)
|
|
|
|
# общие поля
|
|
boss_surname = models.CharField('Фамилия', max_length=30, default='',
|
|
help_text='Используется для строки "подпись" в документах.')
|
|
boss_name = models.CharField('Имя', max_length=30, default='')
|
|
boss_midname = models.CharField('Отчество', max_length=30, default='')
|
|
|
|
# длина: 10 для организаций, 12 для ИП
|
|
inn = models.CharField('ИНН', max_length=12, default='')
|
|
# длина: 13 для организаций, 15 для ИП
|
|
ogrn = models.CharField('ОГРН/ОГРНИП', max_length=15, default='')
|
|
# длина: 8 для организаций, 8 или 10 для ИП
|
|
okpo = models.CharField('ОКПО', max_length=10, blank=True, default='')
|
|
|
|
glavbuh_surname = models.CharField('Фамилия', max_length=30, blank=True, default='',
|
|
help_text='Используется для строки "подпись" в документах.')
|
|
glavbuh_name = models.CharField('Имя', max_length=30, blank=True, default='')
|
|
glavbuh_midname = models.CharField('Отчество', max_length=30, blank=True, default='')
|
|
|
|
address = models.CharField(
|
|
'Фактический адрес',
|
|
max_length=256,
|
|
default='',
|
|
help_text='Будет подставляться в создаваемые счета, акты и накладные.'
|
|
)
|
|
|
|
jur_address = models.CharField('Юридический адрес', max_length=256, blank=True, default='',
|
|
help_text='Как в учредительных документах.')
|
|
|
|
real_address = models.CharField('Почтовый адрес', max_length=256, blank=True, default='',
|
|
help_text='Используется только для карточки компании.')
|
|
|
|
phone_code = models.CharField('Код города', max_length=10, blank=True, default='')
|
|
phone = models.CharField('Номер телефона', max_length=20, blank=True, default='')
|
|
|
|
fax_code = models.CharField('Код города', max_length=10, blank=True, default='')
|
|
fax = models.CharField('Номер телефона', max_length=20, blank=True, default='')
|
|
|
|
email = models.EmailField(
|
|
'Электронная почта',
|
|
max_length=75,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
site = models.CharField(
|
|
'Сайт',
|
|
max_length=256,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
|
|
# поля, только для ИП
|
|
svid_gos_reg = models.CharField(
|
|
'Свид-во о гос. регистрации',
|
|
max_length=256,
|
|
blank=True,
|
|
default='',
|
|
help_text='Требуется для счет-фактуры.'
|
|
)
|
|
|
|
ip_reg_date = models.DateField('Дата регистрации ИП', blank=True, null=True)
|
|
|
|
# поля, только для Организации
|
|
name = models.CharField(
|
|
'Краткое название организации',
|
|
max_length=256,
|
|
default='',
|
|
help_text='Будет подставляться в создаваемые документы.'
|
|
)
|
|
full_name = models.CharField(
|
|
'Полное название организации',
|
|
max_length=256,
|
|
blank=True,
|
|
default='',
|
|
help_text='Как в учредительных документах.'
|
|
)
|
|
|
|
kpp = models.CharField('КПП', max_length=9, default='')
|
|
|
|
boss_title = models.CharField(
|
|
'Должность руководителя',
|
|
max_length=256,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
na_osnovanii = models.CharField(
|
|
'Действует на основании',
|
|
max_length=256,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
|
|
# подписи, печать и логотип
|
|
boss_sign = models.ImageField(
|
|
'Подпись руководителя',
|
|
blank=True,
|
|
default='',
|
|
upload_to=UploadAndRename(PROFILE_IMAGES_UPLOAD_DIR, 'boss_sign.png')
|
|
)
|
|
glavbuh_sign = models.ImageField(
|
|
'Подпись бухгалтера',
|
|
blank=True,
|
|
default='',
|
|
upload_to=UploadAndRename(PROFILE_IMAGES_UPLOAD_DIR, 'glavbuh_sign.png')
|
|
)
|
|
stamp = models.ImageField(
|
|
'Печать',
|
|
blank=True,
|
|
default='',
|
|
upload_to=UploadAndRename(PROFILE_IMAGES_UPLOAD_DIR, 'stamp.png')
|
|
)
|
|
logo = models.ImageField(
|
|
'Логотип',
|
|
blank=True,
|
|
default='',
|
|
upload_to=UploadAndRename(PROFILE_IMAGES_UPLOAD_DIR, 'logo.png')
|
|
)
|
|
|
|
created_at = models.DateTimeField('Создан', auto_now_add=True)
|
|
updated_at = models.DateTimeField('Изменен', auto_now=True)
|
|
active = models.BooleanField('Активен', default=False)
|
|
confirmed = models.BooleanField('Подтверждён', default=False)
|
|
|
|
user_session_key = models.CharField(
|
|
'Ключ сессии (служебная информация)',
|
|
max_length=256,
|
|
blank=True,
|
|
default='',
|
|
help_text='Руками не трогать...'
|
|
)
|
|
|
|
objects = managers.UserProfileManager()
|
|
|
|
class Meta:
|
|
verbose_name = 'Реквизиты (профиль)'
|
|
verbose_name_plural = 'Реквизиты (профили)'
|
|
|
|
def __str__(self):
|
|
return '%s, ИНН %s' % (self.get_company_name()[0:30], self.inn or 'не указан')
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.inn = only_numerics(self.inn)
|
|
self.ogrn = only_numerics(self.ogrn)
|
|
self.okpo = only_numerics(self.okpo)
|
|
self.kpp = only_numerics(self.kpp)
|
|
|
|
def process_img(orig_img, size):
|
|
# TODO http://stackoverflow.com/questions/9166400/convert-rgba-png-to-rgb-with-pil
|
|
w = orig_img.width
|
|
h = orig_img.height
|
|
if w > size[0] or h > size[1]:
|
|
filename = str(orig_img.path)
|
|
img = Image.open(filename)
|
|
img.thumbnail(size, Image.ANTIALIAS)
|
|
img.save(filename, 'PNG')
|
|
|
|
super(UserProfile, self).save(*args, **kwargs)
|
|
|
|
if self.boss_sign:
|
|
process_img(self.boss_sign, size=BOSS_SIGN_IMG_SIZE)
|
|
|
|
if self.glavbuh_sign:
|
|
process_img(self.glavbuh_sign, size=GLAVBUH_SIGN_IMG_SIZE)
|
|
|
|
if self.stamp:
|
|
process_img(self.stamp, size=STAMP_IMG_SIZE)
|
|
|
|
if self.logo:
|
|
process_img(self.logo, size=LOGO_SIZE)
|
|
|
|
def is_ip(self):
|
|
return self.profile_type == consts.IP_PROFILE
|
|
|
|
def is_org(self):
|
|
return self.profile_type == consts.ORG_PROFILE
|
|
|
|
def check_name_not_filled(self):
|
|
"""`ИП ФИО` или `Название Организации`."""
|
|
if self.is_ip():
|
|
return self.get_boss_full_fio().strip() == ''
|
|
elif self.is_org():
|
|
return self.name.strip() == ''
|
|
return False
|
|
|
|
def get_main_bank_account(self):
|
|
try:
|
|
bank_accounts = BankAccount.objects.filter(company=self, is_main=True).first()
|
|
return bank_accounts
|
|
except BankAccount.DoesNotExist:
|
|
return None
|
|
|
|
def get_first_user(self):
|
|
try:
|
|
first_user = DokUser.objects.filter(profile=self)[0]
|
|
return first_user
|
|
except DokUser.DoesNotExist:
|
|
return None
|
|
|
|
def check_main_reqs_not_filled(self):
|
|
result = self.check_name_not_filled() or self.inn == '' or self.address == '' or \
|
|
self.get_boss_fio() == '' or self.get_main_bank_account() == '' # noqa
|
|
if result:
|
|
return True
|
|
|
|
if self.is_ip():
|
|
return self.ogrn == ''
|
|
elif self.is_org():
|
|
return self.kpp == ''
|
|
|
|
def get_company_name(self):
|
|
"""
|
|
`ИП ФИО` или `Название Организации`.
|
|
"""
|
|
if self.profile_type == consts.IP_PROFILE:
|
|
return 'ИП %s' % self.get_boss_full_fio()
|
|
elif self.profile_type == consts.ORG_PROFILE:
|
|
return self.name.strip()
|
|
return ''
|
|
|
|
def get_inn_and_kpp(self):
|
|
"""Возвращает пару ИНН/КПП или только ИНН, если это ИП или КПП не заполнен."""
|
|
if self.profile_type == consts.ORG_PROFILE:
|
|
kpp = self.kpp.strip()
|
|
if kpp:
|
|
return '%s/%s' % (self.inn, kpp,)
|
|
return self.inn
|
|
|
|
def get_boss_title(self):
|
|
"""Текст 'Индивидуальный предприниматель' или 'Руководитель организации'."""
|
|
if self.profile_type == consts.IP_PROFILE:
|
|
return 'Индивидуальный предприниматель'
|
|
elif self.profile_type == consts.ORG_PROFILE:
|
|
return 'Руководитель организации'
|
|
return ''
|
|
|
|
def get_boss_fio(self):
|
|
"""Фамилия и инициалы руководителя ИП/организации."""
|
|
if self.boss_surname and self.boss_name and self.boss_midname:
|
|
return '%s %s.%s.' % (self.boss_surname, self.boss_name[0], self.boss_midname[0],)
|
|
return ''
|
|
|
|
def get_boss_full_fio(self):
|
|
"""Полное ФИО руководителя ИП/организации."""
|
|
return ('%s %s %s' % (self.boss_surname, self.boss_name, self.boss_midname,)).strip()
|
|
|
|
def get_glavbuh_fio(self):
|
|
"""Фамилия и инициалы главного бухгалтера."""
|
|
if self.glavbuh_surname and self.glavbuh_name and self.glavbuh_midname:
|
|
return f'{self.glavbuh_surname} {self.glavbuh_name[0]}. {self.glavbuh_midname[0]}.'
|
|
return ''
|
|
|
|
def get_glavbuh_full_fio(self):
|
|
"""Полное ФИО главного бухгалтера."""
|
|
return f'{self.glavbuh_surname} {self.glavbuh_name} {self.glavbuh_midname}'
|
|
|
|
def get_full_phone(self):
|
|
"""(Код города) Номер телефона."""
|
|
if self.phone_code:
|
|
phone_code = self.phone_code.strip('() ')
|
|
phone_code = f'({phone_code})'
|
|
if self.phone and self.phone_code:
|
|
return f'{phone_code} {self.phone}'
|
|
|
|
def get_email(self):
|
|
try:
|
|
return self.get_first_user().email
|
|
except:
|
|
return None
|
|
|
|
def get_full_fax(self):
|
|
"""(Код города) Номер факса."""
|
|
fax_code = self.fax_code.strip('() ')
|
|
fax_code = '(%s)' % fax_code if fax_code else fax_code
|
|
return ('%s %s' % (fax_code, self.fax,)).strip()
|
|
|
|
def validate_has_profile_account(self):
|
|
"""
|
|
Check there account from this profile
|
|
:return: True or False
|
|
"""
|
|
if self.bank_accounts.all():
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
class BankAccount(models.Model):
|
|
"""Расчетные счета."""
|
|
company = models.ForeignKey(UserProfile, related_name='bank_accounts')
|
|
|
|
bik = models.CharField('БИК', max_length=10)
|
|
name = models.CharField('Наименование банка', max_length=256)
|
|
short_name = models.CharField(
|
|
'Сокращенное название банка',
|
|
max_length=100,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
korr_account = models.CharField('Корр. счет', max_length=20)
|
|
account = models.CharField('Расчетный счет', max_length=20)
|
|
|
|
is_main = models.BooleanField('Основной счет', default=False)
|
|
address = models.CharField('Местонахождение', max_length=256, blank=True,
|
|
default='') # TODO delete field?
|
|
|
|
created_at = models.DateTimeField('Создан', auto_now_add=True)
|
|
updated_at = models.DateTimeField('Изменен', auto_now=True)
|
|
|
|
objects = managers.BankAccountManager()
|
|
|
|
class Meta:
|
|
verbose_name = 'Расчётный счет'
|
|
verbose_name_plural = 'Расчётные счета'
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f'{self.account}, {self.short_name[0:30] or self.name[0:30]}'
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.bik = only_numerics(self.bik)
|
|
self.korr_account = only_numerics(self.korr_account)
|
|
self.account = only_numerics(self.account)
|
|
|
|
super(BankAccount, self).save(*args, **kwargs)
|
|
|
|
if self.is_main:
|
|
# если задано, что это будет основной счет, то
|
|
# сбросить у остальных счетов пользователя этот признак
|
|
BankAccount.objects.filter(
|
|
company=self.company, is_main=True).exclude(pk=self.pk).update(is_main=False)
|
|
else:
|
|
# если нет основного счета, то установить его принудительно
|
|
BankAccount.objects.force_main(company=self.company)
|
|
|
|
def delete(self, *args, **kwargs):
|
|
super(BankAccount, self).delete(*args, **kwargs)
|
|
# если нет основного счета, то установить его принудительно
|
|
BankAccount.objects.force_main(company=self.company)
|
|
|
|
|
|
class Client(models.Model):
|
|
"""Контрагенты."""
|
|
company = models.ForeignKey(UserProfile, related_name='clients')
|
|
|
|
name = models.CharField('Наименование', max_length=256, db_index=True)
|
|
|
|
name_short_self = models.CharField(
|
|
'Короткое наименование',
|
|
max_length=256,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
name_short_dadata = models.CharField(
|
|
'Наименование из Dadata',
|
|
max_length=256,
|
|
null=True,
|
|
blank=True
|
|
)
|
|
|
|
inn = models.CharField('ИНН', max_length=12)
|
|
kpp = models.CharField('КПП', max_length=9, blank=True, default='') # Организация
|
|
ogrn = models.CharField('ОГРН', max_length=15, default='')
|
|
okpo = models.CharField('ОКПО', max_length=10, blank=True, default='') # ИП
|
|
address = models.CharField('Юр. адрес', max_length=256)
|
|
|
|
# банковские реквизиты
|
|
bank_bik = models.CharField('БИК', max_length=10, blank=True, default='')
|
|
bank_name = models.CharField('Наименование банка', max_length=256, blank=True, default='')
|
|
bank_short_name = models.CharField(
|
|
'Сокращенное наименование банка',
|
|
max_length=256,
|
|
blank=True,
|
|
default=''
|
|
)
|
|
bank_korr_account = models.CharField('Корр. счет', max_length=20, blank=True, default='')
|
|
bank_account = models.CharField('Расчетный счет', max_length=20, blank=True, default='')
|
|
bank_address = models.CharField('Местонахождение', max_length=256, blank=True,
|
|
default='') # TODO delete field?
|
|
|
|
# контакты
|
|
contact_name = models.CharField('Имя', max_length=50, blank=True, default='')
|
|
contact_email = models.EmailField('E-mail', max_length=50, blank=True, default='')
|
|
contact_phone = models.CharField('Телефон', max_length=50, blank=True, default='')
|
|
contact_skype = models.CharField('Skype', max_length=20, blank=True, default='')
|
|
contact_other = models.CharField('Другое', max_length=256, blank=True, default='')
|
|
|
|
created_at = models.DateTimeField('Создан', auto_now_add=True)
|
|
updated_at = models.DateTimeField('Изменен', auto_now=True)
|
|
|
|
objects = managers.ClientManager()
|
|
|
|
class Meta:
|
|
verbose_name = 'Контрагент'
|
|
verbose_name_plural = 'Контрагенты'
|
|
ordering = ['name', '-created_at']
|
|
|
|
def __str__(self):
|
|
if self.name_short_self:
|
|
return f'{self.name_short_dadata}, {self.name_short_self}'
|
|
else:
|
|
return f'{self.name}, ИНН {self.inn or "не указан"}'
|
|
|
|
def save(self, *args, **kwargs):
|
|
self.inn = only_numerics(self.inn)
|
|
self.kpp = only_numerics(self.kpp)
|
|
self.ogrn = only_numerics(self.ogrn)
|
|
self.okpo = only_numerics(self.okpo)
|
|
self.bank_bik = only_numerics(self.bank_bik)
|
|
self.bank_korr_account = only_numerics(self.bank_korr_account)
|
|
self.bank_account = only_numerics(self.bank_account)
|
|
super(Client, self).save(*args, **kwargs)
|
|
|
|
def get_inn_and_kpp(self):
|
|
"""Возвращает пару ИНН/КПП или только ИНН, если КПП не заполнен."""
|
|
kpp = self.kpp.strip()
|
|
if kpp:
|
|
return f'{self.inn}/{kpp}'
|
|
return self.inn
|
|
|
|
@property
|
|
def short_name(self):
|
|
if self.name_short_dadata:
|
|
return self.name_short_dadata
|
|
else:
|
|
return self.name
|
|
|
|
|
|
class UserProfileFilters(models.Model):
|
|
"""
|
|
Фильтрация реквизитов: какие данные показывать/скрывать при генерации карточки компании.
|
|
"""
|
|
company = models.OneToOneField(UserProfile, related_name='profile_filters', primary_key=True)
|
|
|
|
# общие фильтры
|
|
show_profile_type = models.BooleanField('Тип профиля', default=True)
|
|
|
|
show_inn = models.BooleanField('ИНН', default=True)
|
|
show_ogrn = models.BooleanField('ОГРН/ОГРНИП', default=True)
|
|
show_okpo = models.BooleanField('ОКПО', default=True)
|
|
|
|
show_glavbuh = models.BooleanField('Главный бухгалтер', default=True)
|
|
|
|
show_bank_account = models.BooleanField('Банковские реквизиты', default=True)
|
|
bank_account = models.ForeignKey(
|
|
BankAccount,
|
|
related_name='+',
|
|
verbose_name='Расчетный счет',
|
|
blank=True,
|
|
null=True,
|
|
default=None
|
|
)
|
|
|
|
show_contact_info = models.BooleanField('Контактная информация', default=True)
|
|
show_address = models.BooleanField('Фактический адрес', default=True)
|
|
show_jur_address = models.BooleanField('Юридический адрес', default=True)
|
|
show_real_address = models.BooleanField('Почтовый адрес', default=True)
|
|
show_phone = models.BooleanField('Телефон', default=True)
|
|
show_fax = models.BooleanField('Факс', default=True)
|
|
show_email = models.BooleanField('Электронная почта', default=True)
|
|
show_site = models.BooleanField('Сайт', default=True)
|
|
|
|
show_logo = models.BooleanField('Логотип', default=True)
|
|
|
|
# только для ИП
|
|
show_ip_boss_fio = models.BooleanField('Фамилия, Имя, Отчество', default=True)
|
|
show_svid_gos_reg = models.BooleanField('Свид-во о гос. регистрации', default=True)
|
|
show_ip_reg_date = models.BooleanField('Дата регистрации ИП', default=True)
|
|
|
|
# только для Организации
|
|
show_name = models.BooleanField('Краткое название организации', default=True)
|
|
show_full_name = models.BooleanField('Полное название организации', default=True)
|
|
show_kpp = models.BooleanField('КПП', default=True)
|
|
show_org_boss_title_and_fio = models.BooleanField(
|
|
'Руководитель (Должность, ФИО)', default=True)
|
|
show_na_osnovanii = models.BooleanField('Действует на основании', default=True)
|
|
|
|
objects = managers.UserProfileFiltersManager()
|
|
|
|
class Meta:
|
|
verbose_name = 'Фильтры реквизитов'
|
|
verbose_name_plural = 'Фильтры реквизитов'
|
|
|
|
def __str__(self): # TODO fix name
|
|
return f'{self.company.email}'
|
|
|
|
def save(self, *args, **kwargs):
|
|
# всегда включены
|
|
self.show_ip_boss_fio = True
|
|
self.show_name = True
|
|
super(UserProfileFilters, self).save(*args, **kwargs)
|
|
|
|
|
|
class License(models.Model):
|
|
company = models.ForeignKey(
|
|
UserProfile,
|
|
related_name='licenses',
|
|
verbose_name='пользователь'
|
|
)
|
|
term = models.IntegerField(verbose_name='срок лицензии')
|
|
date_from = models.DateField('дата начала', null=True, blank=True)
|
|
date_to = models.DateField('дата окончания', null=True, blank=True)
|
|
payform = models.IntegerField(verbose_name='форма оплаты',
|
|
choices=consts.PAYFORMS, default=0)
|
|
status = models.IntegerField(verbose_name='статус лицензии',
|
|
choices=consts.LICENSE_STATUSES, default=0)
|
|
order_date = models.DateField(verbose_name='дата заказа', auto_now_add=True)
|
|
paid_date = models.DateField(verbose_name='дата оплаты', null=True, blank=True)
|
|
pay_sum = models.IntegerField(verbose_name='сумма оплаты')
|
|
deleted = models.BooleanField('удалено', default=False)
|
|
|
|
class Meta:
|
|
verbose_name = 'Лицензии'
|
|
verbose_name_plural = 'Лицензии'
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(License, self).__init__(*args, **kwargs)
|
|
self.__prev_date = self.paid_date
|
|
|
|
def __str__(self):
|
|
return '%s - %s %s (%d %s)' % (
|
|
self.company.get_company_name(),
|
|
self.term,
|
|
numeral.choose_plural(self.term, "месяц, месяца, месяцев"),
|
|
self.pay_sum,
|
|
numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей"),
|
|
)
|
|
|
|
def get_payment_id(self):
|
|
if self.payform == 1:
|
|
payments = Payment.objects.filter(order_number=self.id)
|
|
if payments:
|
|
return payments.first().id
|
|
|
|
def get_payment(self):
|
|
if self.payform == 1:
|
|
payments = Payment.objects.filter(order_number=self.id)
|
|
if payments:
|
|
return payments.first()
|
|
|
|
# TODO: test
|
|
def save(self, *args, **kwargs):
|
|
|
|
if not self.__prev_date and self.paid_date:
|
|
max_date_license = License.objects.\
|
|
filter(company=self.company).aggregate(Max('date_to'))['date_to__max']
|
|
today = datetime.now().date()
|
|
if max_date_license < today:
|
|
max_date_license = today - timedelta(1)
|
|
self.date_from = max_date_license + relativedelta(days=1)
|
|
self.date_to = self.date_from + relativedelta(months=self.term, days=-1)
|
|
self.company.active = True
|
|
self.company.save()
|
|
self.status = 1
|
|
utils.check_one_profile(self.company, datetime.now(), manual=True)
|
|
|
|
super(License, self).save(*args, **kwargs)
|
|
|
|
def get_company(self):
|
|
return self.company.get_company_name()
|
|
|
|
def get_action_link(self):
|
|
|
|
if self.status == 0:
|
|
|
|
if self.payform == 0:
|
|
url = reverse(
|
|
'get_payment_account',
|
|
kwargs={'order_num': self.id}
|
|
)
|
|
return f'<a href="{url}">Оплата безналичным платежом</a>'
|
|
|
|
elif self.payform == 1:
|
|
if self.get_payment():
|
|
url = get_robokassa_url(self.get_payment())
|
|
else:
|
|
url = '#'
|
|
return f'<a href="{url}">Оплата картой, электронными деньгами, наличными</a>'
|
|
|
|
elif self.status in [1, 2]:
|
|
url = reverse(
|
|
'get_certificate_of_completion',
|
|
kwargs={'order_num': self.id}
|
|
)
|
|
cost_str = f'{self.pay_sum} ' \
|
|
f'{numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей")}'
|
|
return f'<a href="{url}">Скачать акт № {self.id} на {cost_str}</a>'
|
|
|
|
elif self.status == 4:
|
|
|
|
if self.payform == 0:
|
|
return f'<a href="#" class="not-active">Оплата безналичным платежом</a>'
|
|
|
|
elif self.payform == 1:
|
|
return f'<a href="#" class="not-active">Оплата картой, ' \
|
|
f'электронными деньгами, наличными</a>'
|
|
|
|
else:
|
|
return ''
|
|
|
|
def get_term(self):
|
|
if self.term == 0:
|
|
return '45 дней'
|
|
else:
|
|
return f'{self.term} {numeral.choose_plural(self.term, "месяц, месяца, месяцев")}'
|
|
|
|
def get_paid_status(self):
|
|
if self.status == 1:
|
|
return 'Лицензия выдана, ещё не активирована'
|
|
elif self.status in [2, -1]:
|
|
left = relativedelta(self.date_to, timezone.now().date())
|
|
if left.months:
|
|
left_str = f'{left.months} ' \
|
|
f'{numeral.choose_plural(left.months, "месяц, месяца, месяцев")} ' \
|
|
f'{left.days} {numeral.choose_plural(left.days, "день, дня, дней")}'
|
|
else:
|
|
left_str = f'{left.days} {numeral.choose_plural(left.days, "день, дня, дней")}'
|
|
remain_str = numeral.choose_plural(left.days, "остался, осталось, осталось")
|
|
return f'Лицензия активна, {remain_str} {left_str}'
|
|
elif self.status == 3:
|
|
return 'Время истекло'
|
|
else:
|
|
return None
|
|
|
|
@property
|
|
def account_status(self):
|
|
|
|
if self.status in [0, 4]:
|
|
return 'Счет не оплачен'
|
|
else:
|
|
return 'Счет оплачен'
|
|
|
|
@property
|
|
def account_sub_status(self):
|
|
if self.status == 4:
|
|
freeze_date = self.order_date + timezone.timedelta(5)
|
|
return f'Счет заморожен {freeze_date.strftime("%d.%m.%Y")}'
|
|
if self.status == 0:
|
|
remain_day = relativedelta(self.order_date + timezone.timedelta(5),
|
|
timezone.now().date())
|
|
remain_day_str = f'{remain_day.days} ' \
|
|
f'{numeral.choose_plural(remain_day.days, "день, дня, дней")}'
|
|
remain_str = numeral.choose_plural(remain_day.days, "Остался, Осталось, Осталось")
|
|
if remain_day.days == 0:
|
|
return 'Завтра счет будет заморожен'
|
|
else:
|
|
return f'{remain_str} {remain_day_str}'
|
|
|
|
@property
|
|
def is_status_need_to_change(self):
|
|
if self.status == 0:
|
|
remain_day = relativedelta(self.order_date + timezone.timedelta(5),
|
|
timezone.now().date())
|
|
if remain_day.days <= 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def set_freeze_status(self):
|
|
try:
|
|
self.status = 4
|
|
self.save()
|
|
return True
|
|
except Exception as e:
|
|
log.info(f"don't change status.Error {e}")
|
|
return False
|
|
|
|
|
|
class LicensePrice(models.Model):
|
|
term = models.IntegerField(verbose_name='срок лицензии',
|
|
choices=consts.TERMS)
|
|
price = models.IntegerField(verbose_name='сумма оплаты')
|
|
|
|
class Meta:
|
|
verbose_name = 'Прайс на лицензии'
|
|
verbose_name_plural = 'Прайсы на лицензии'
|
|
|
|
def __str__(self):
|
|
return f'{self.term} {numeral.choose_plural(self.term, "месяц, месяца, месяцев")} ' \
|
|
f'({self.price} {numeral.choose_plural(self.price, "рубль, рубля, рублей")})'
|
|
|
|
|
|
class Payment(models.Model):
|
|
PROCESSED = 0
|
|
SUCCESS = 1
|
|
FAIL = 2
|
|
|
|
CHOICES = (
|
|
(PROCESSED, 'Ожидает оплаты'),
|
|
(SUCCESS, 'Оплачен'),
|
|
(FAIL, 'Отклонен'),
|
|
)
|
|
|
|
BANK = 0
|
|
CARD = 1
|
|
|
|
CHOICES_TYPE = (
|
|
(BANK, 'Безналичный расчёт'),
|
|
(CARD, 'Банковская карта'),
|
|
)
|
|
|
|
order_amount = models.DecimalField('Сумма заказа', max_digits=15, decimal_places=2)
|
|
order_number = models.IntegerField('Номер заказа')
|
|
user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
related_name='payment_user',
|
|
verbose_name='Пользователь'
|
|
)
|
|
created = models.DateTimeField('Время создания', auto_now_add=True)
|
|
status = models.PositiveSmallIntegerField(
|
|
verbose_name='Статус',
|
|
choices=CHOICES,
|
|
default=PROCESSED
|
|
)
|
|
type = models.PositiveSmallIntegerField(
|
|
verbose_name='Тип платежа',
|
|
choices=CHOICES_TYPE,
|
|
default=BANK
|
|
)
|
|
date = models.DateField(verbose_name='Дата', null=True, blank=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'Платеж'
|
|
verbose_name_plural = 'Платежи'
|
|
|
|
def __str__(self):
|
|
return f'{self.user}-{self.order_number}-{self.get_status_display()}'
|
|
|
|
def save(self, *args, **kwargs):
|
|
# if manual setup
|
|
if self.status == self.SUCCESS:
|
|
if self.date is None:
|
|
self.date = datetime.now().date()
|
|
payment = super(Payment, self).save(*args, **kwargs)
|
|
return payment
|
|
|
|
|
|
def confirmed_purchase(**kwargs):
|
|
try:
|
|
payment = Payment.objects.get(
|
|
pk=kwargs['InvId'],
|
|
order_amount=kwargs['OutSum']
|
|
)
|
|
payment.status = Payment.SUCCESS
|
|
payment.date = datetime.now().date()
|
|
payment.save()
|
|
|
|
except Payment.DoesNotExist:
|
|
log.info(f"payment with id={kwargs['InvId']} not found")
|
|
|
|
|
|
# Robokassa
|
|
@receiver(result_received)
|
|
def order_completed(sender, **kwargs):
|
|
confirmed_purchase(**kwargs)
|
|
|
|
|
|
# Robokassa for local debugging
|
|
@receiver(success_page_visited)
|
|
def success_page_visited_completed(sender, **kwargs):
|
|
if settings.DEBUG:
|
|
confirmed_purchase(**kwargs)
|
|
|
|
|
|
@receiver(post_save, sender=Payment)
|
|
def check_license_dependence(sender, **kwargs):
|
|
if not kwargs.get('created'):
|
|
payment = kwargs.get('instance')
|
|
if payment.status == Payment.SUCCESS and payment.date:
|
|
try:
|
|
lic = License.objects.get(pk=payment.order_number)
|
|
lic.status = consts.STATUS_PAID
|
|
lic.paid_date = payment.date
|
|
lic.save()
|
|
|
|
emails.send_license_successful_purchased.delay(
|
|
payment.user.email,
|
|
lic.pk,
|
|
lic.get_term(),
|
|
lic.date_from.strftime("%d.%m.%Y")
|
|
)
|
|
|
|
payment.user.profile.active = True
|
|
payment.user.profile.save()
|
|
|
|
bonus_license = create_bonus_license(lic)
|
|
if bonus_license:
|
|
emails.send_bonus_license_issued.delay(
|
|
payment.user.email,
|
|
lic.pk,
|
|
lic.get_term(),
|
|
bonus_license.get_term(),
|
|
bonus_license.date_from.strftime("%d.%m.%Y")
|
|
)
|
|
except License.DoesNotExist:
|
|
log.info(f"payment with id={kwargs['InvId']} not found")
|
|
|