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.
 
 
 
 

798 lines
31 KiB

# -*- coding: utf-8 -*-
import os
import logging
from datetime import datetime, timedelta
from PIL import Image
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 myauth.models import DokUser
from commons.utils import only_numerics
from robokassa.signals import result_received, success_page_visited
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):
"""(Код города) Номер телефона."""
phone_code = self.phone_code.strip('() ')
phone_code = f'({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)
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='')
# контакты
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
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
# 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>'
# redirect to pay terminal with data
elif self.payform == 1:
if self.get_payment_id():
url = reverse('payment_robokassa',
kwargs={'payment_id': self.get_payment_id()})
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">Оплата банковской картой</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, 'Отклонен'),
)
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
)
class Meta:
verbose_name = 'Платеж'
verbose_name_plural = 'Платежи'
def __str__(self):
return f'{self.user}-{self.order_number}-{self.get_status_display()}'
@receiver(result_received)
def order_completed(sender, **kwargs):
try:
payment = Payment.objects.get(
pk=kwargs['InvId'],
order_amount=kwargs['OutSum']
)
payment.status = Payment.SUCCESS
payment.save()
lic = License.objects.get(pk=payment.order_number)
lic.status = consts.STATUS_PAID
lic.paid_date = datetime.now().date()
lic.save()
except Payment.DoesNotExist:
log.info(f"payment with id={kwargs['InvId']} not found")
except License.DoesNotExist:
log.info(f"payment with id={kwargs['InvId']} not found")
@receiver(success_page_visited)
def success_page_visited_completed(sender, **kwargs):
if settings.DEBUG:
try:
payment = Payment.objects.get(
pk=kwargs['InvId'],
order_amount=kwargs['OutSum']
)
payment.status = Payment.SUCCESS
payment.save()
lic = License.objects.get(pk=payment.order_number)
lic.status = consts.STATUS_PAID
lic.paid_date = datetime.now().date()
lic.save()
except Payment.DoesNotExist:
log.info(f"payment with id={kwargs['InvId']} not found")
except License.DoesNotExist:
log.info(f"payment with id={kwargs['InvId']} not found")