diff --git a/src/customer/admin.py b/src/customer/admin.py index 34ba314..c38ac04 100644 --- a/src/customer/admin.py +++ b/src/customer/admin.py @@ -6,12 +6,7 @@ from customer import forms from customer import models -class UserProfileAdmin(admin.ModelAdmin): - list_display = ('get_email', 'profile_type', 'name', 'inn', 'active') - list_display_links = list_display - form = forms.UserProfileAdminForm - - +@admin.register(models.License) class LicenseAdmin(admin.ModelAdmin): list_display = ( 'user_email', @@ -21,12 +16,14 @@ class LicenseAdmin(admin.ModelAdmin): 'order_date', 'date_from', 'date_to', - 'balance_days' + 'balance_days', + 'deleted' ) list_display_links = list_display search_fields = ('company__email',) - list_filter = ('status', 'term', 'order_date', 'date_from', 'date_to') + list_filter = ('status', 'term', 'order_date', 'date_from', 'date_to', 'deleted') + readonly_fields = ('company', 'term', ) def user_email(self, obj): if obj.company.users.first(): @@ -56,6 +53,12 @@ class LicenseAdmin(admin.ModelAdmin): balance_days.short_description = 'Остаток дней' +class UserProfileAdmin(admin.ModelAdmin): + list_display = ('get_email', 'profile_type', 'name', 'inn', 'active') + list_display_links = list_display + form = forms.UserProfileAdminForm + + class BankAccountAdmin(admin.ModelAdmin): class Media: css = {'all': ('css/custom_admin.css',)} @@ -89,5 +92,4 @@ class ClientAdmin(admin.ModelAdmin): admin.site.register(models.UserProfile, UserProfileAdmin) admin.site.register(models.BankAccount, BankAccountAdmin) admin.site.register(models.Client, ClientAdmin) -admin.site.register(models.License, LicenseAdmin) admin.site.register(models.LicensePrice) diff --git a/src/customer/consts.py b/src/customer/consts.py index 7b58d5a..28662ce 100644 --- a/src/customer/consts.py +++ b/src/customer/consts.py @@ -8,13 +8,20 @@ PROFILE_TYPES = ( (ORG_PROFILE, 'Организация'), ) +STATUS_TRAIL = -1 +STATUS_NOT_PAID = 0 +STATUS_PAID = 1 +STATUS_ACTIVATED = 2 +STATUS_EXPIRED = 3 +STATUS_FROZEN = 4 + LICENSE_STATUSES = ( - (-1, 'Пробный период'), - (0, 'Не оплачен'), - (1, 'Оплачен'), - (2, 'Активирован'), - (3, 'Срок действия истёк'), - (4, 'Заморожен'), + (STATUS_TRAIL, 'Пробный период'), + (STATUS_NOT_PAID, 'Не оплачен'), + (STATUS_PAID, 'Оплачен'), + (STATUS_ACTIVATED, 'Активирован'), + (STATUS_EXPIRED, 'Срок действия истёк'), + (STATUS_FROZEN, 'Заморожен'), ) PAYFORMS = ( diff --git a/src/customer/context_processors.py b/src/customer/context_processors.py index 5aee1f4..d489b0f 100644 --- a/src/customer/context_processors.py +++ b/src/customer/context_processors.py @@ -15,8 +15,6 @@ def license_check_soon_ends(request): days_left = cache.get(f'days_left_{request.user.username}', None) cur_license = cache.get(f'cur_license_{request.user.username}', None) - print('license_15days=', license_15days) - if not days_left or not cur_license: now = datetime.today() cur_license = License.objects.filter( diff --git a/src/customer/models.py b/src/customer/models.py index a461ab4..344d4a2 100644 --- a/src/customer/models.py +++ b/src/customer/models.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- import os + +import logging + from datetime import datetime, timedelta +from django.utils import timezone from PIL import Image from pytils import numeral @@ -15,6 +19,8 @@ from myauth.models import DokUser from commons.utils import only_numerics from django.utils.deconstruct import deconstructible +log = logging.getLogger(__name__) + # куда сохранять загруженные изображения PROFILE_IMAGES_UPLOAD_DIR = 'customer/profile/' @@ -365,11 +371,8 @@ class BankAccount(models.Model): verbose_name_plural = 'Расчётные счета' ordering = ['-created_at'] - def __unicode__(self): - return ('%s, %s' % (self.account, self.short_name[0:30] or self.name[0:30],)).strip() - def __str__(self): - return ('%s, %s' % (self.account, self.short_name[0:30] or self.name[0:30],)).strip() + 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) @@ -447,17 +450,11 @@ class Client(models.Model): verbose_name_plural = 'Контрагенты' ordering = ['name', '-created_at'] - def __unicode__(self): - if self.name_short_self: - return ('%s, %s' % (self.name_short_dadata, self.name_short_self)).strip() - else: - return ('%s, ИНН %s' % (self.name, self.inn or 'не указан',)).strip() - def __str__(self): if self.name_short_self: - return ('%s, %s' % (self.name_short_dadata, self.name_short_self)).strip() + return f'{self.name_short_dadata}, {self.name_short_self}' else: - return ('%s, ИНН %s' % (self.name, self.inn or 'не указан',)).strip() + return f'{self.name}, ИНН {self.inn or "не указан"}' def save(self, *args, **kwargs): self.inn = only_numerics(self.inn) @@ -473,7 +470,7 @@ class Client(models.Model): """Возвращает пару ИНН/КПП или только ИНН, если КПП не заполнен.""" kpp = self.kpp.strip() if kpp: - return '%s/%s' % (self.inn, kpp,) + return f'{self.inn}/{kpp}' return self.inn @@ -577,12 +574,13 @@ class License(models.Model): numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей"), ) + # 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() + today = timedelta.now().date() if max_date_license < today: max_date_license = today - timedelta(1) self.date_from = max_date_license + relativedelta(days=1) @@ -600,16 +598,32 @@ class License(models.Model): def get_action_link(self): if self.status == 0: + if self.payform == 0: url = reverse('customer_license_get_doc', kwargs={'order_num': self.id}) - return f'Скачать счёт' + return f'Оплата безналичным платежом' + elif self.payform == 1: - return 'Оплатить счёт' + return f'Оплата банковской картой' + elif self.payform == 2: url = reverse('customer_license_get_doc', kwargs={'order_num': self.id}) return f'Скачать квитанцию' + elif self.status in [1, 2]: - return 'История операций' + url = '#' + cost_str = f'{self.pay_sum} ' \ + f'{numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей")}' + return f'Скачать акт № {self.id} на {cost_str}' + + elif self.status == 4: + + if self.payform == 0: + return f'Оплата безналичным платежом' + + elif self.payform == 1: + return f'Оплата банковской картой' + else: return '' @@ -621,21 +635,65 @@ class License(models.Model): def get_paid_status(self): if self.status == 1: - return 'Лицензия оплачена, ещё не активирована' + return 'Лицензия выдана, ещё не активирована' elif self.status in [2, -1]: - left = relativedelta(self.date_to, datetime.today()) + 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, "день, дня, дней")}' - return f'Лицензия активирована: осталось {left_str}' + 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, 1, 4]: + 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 status_need_to_change(self): + if self.status == 0: + remain_day = relativedelta(self.order_date + timezone.timedelta(5), + timezone.now().date()) + if remain_day == 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='срок лицензии', diff --git a/src/customer/tasks.py b/src/customer/tasks.py index aa426f3..ec79271 100644 --- a/src/customer/tasks.py +++ b/src/customer/tasks.py @@ -83,3 +83,24 @@ def send_offer_for_get_bonus(): user_list.append(user) emails.send_offer_buy_licence_for_get_bonus.delay(user.email, url) return user_list + + +@shared_task +def check_not_paid_accounts(): + """ + Check the unpaid bills(instance of license) and if their period + more than 5 days change their status to frozen + :return: list of change license (accounts) + """ + account_list = [] + not_paid_accounts_list = License.objects.filter( + deleted=False, + status=0).order_by('-id') + if not_paid_accounts_list: + + for account in not_paid_accounts_list: + if account.status_need_to_change: + account.set_freeze_status() + account_list.append(account) + + return account_list diff --git a/src/customer/urls.py b/src/customer/urls.py index dd1aa30..01b7a71 100644 --- a/src/customer/urls.py +++ b/src/customer/urls.py @@ -24,8 +24,11 @@ urlpatterns = [ url(r'^payment/result/$', license.payment_result, name='yamoney_result'), url(r'^payment/success/$', license.payment_success, name='yamoney_success'), url(r'^payment/fail/$', license.payment_fail, name='yamoney_fail'), + # for delete url(r'^license_list/$', license.license_list, name='customer_license_list'), url(r'^paid_list/$', license.paid_list, name='customer_paid_list'), + # for delete end + url(r'^orders/$', license.orders_list, name='customer-orders'), # --- профиль AJAX url(r'^profile/filters/edit/ajax/$', profile_ajax.profile_filters_edit_ajax, diff --git a/src/customer/views/documents.py b/src/customer/views/documents.py index 4e8b41d..065cef8 100644 --- a/src/customer/views/documents.py +++ b/src/customer/views/documents.py @@ -28,7 +28,7 @@ def get_doc(request, order_num=None): data = request.user.profile if pm == 0: tmp_name = 'bill.xls' - file_name = "Invoice No.{order_num}.xls" + file_name = f"Invoice No.{order_num}.xls" elif pm == 2: tmp_name = '4pd.xls' file_name = f"Kvitanciya na oplatu zakaza No.{order_num}.xls" diff --git a/src/customer/views/license.py b/src/customer/views/license.py index d462eaf..4d72f6d 100644 --- a/src/customer/views/license.py +++ b/src/customer/views/license.py @@ -2,6 +2,9 @@ import json import hashlib +import itertools + +from django.db.models import Count from django.shortcuts import render, redirect from django.http import (HttpResponseForbidden, HttpResponse, HttpResponseBadRequest) from django.conf import settings @@ -163,3 +166,25 @@ def payment_fail(request): {'message': message, 'success': False}) except: return HttpResponseForbidden() + + +@login_required +def orders_list(request): + """List license and account for license in one page""" + raise_if_no_profile(request) + template_name = 'customer/profile/orders_list.html' + + accounts_list = License.objects.filter( + company=request.user.profile, + deleted=False, + status__gt=-1).\ + annotate(type=Count('status')).order_by('-id') + + licenses_list = License.objects.filter( + company=request.user.profile, + status__in=[-1, 1, 2, 3], + deleted=False).order_by('-id') + + merged = itertools.chain(accounts_list, licenses_list) + context = {'object_list': merged} + return render(request, template_name, context) diff --git a/src/dokumentor/settings/common.py b/src/dokumentor/settings/common.py index 5ab0357..e712af6 100644 --- a/src/dokumentor/settings/common.py +++ b/src/dokumentor/settings/common.py @@ -278,6 +278,10 @@ CELERYBEAT_SCHEDULE = { 'task': 'src.customer.tasks.send_offer_for_get_bonus', 'schedule': timedelta(days=1), }, + 'check_not_paid_accounts': { + 'task': 'src.customer.tasks.check_not_paid_accounts', + 'schedule': timedelta(days=1), + }, } CAPTCHA_OUTPUT_FORMAT = \ diff --git a/src/factories/models.py b/src/factories/models.py index 780e627..ba24837 100755 --- a/src/factories/models.py +++ b/src/factories/models.py @@ -101,7 +101,7 @@ class LicenseFactory(factory.django.DjangoModelFactory): start_date='-30d', tzinfo=pytz.UTC ) - pay_sum = factory.Iterator([1000, 2000]) + pay_sum = factory.Iterator([100, 200, 1000, 2000]) deleted = False class Meta: diff --git a/src/tests/test_models.py b/src/tests/test_models.py new file mode 100644 index 0000000..3060d99 --- /dev/null +++ b/src/tests/test_models.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +import pytest + +from freezegun import freeze_time + +from django.utils import timezone + +from customer import consts + + +@pytest.mark.parametrize('status', range(1, 5)) +@pytest.mark.django_db +def test_license_get_term(lic, status): + lic.status = status + lic.save() + assert 'дней' not in lic.get_term() + + +@pytest.mark.parametrize('status', [0, 1, 4]) +@pytest.mark.django_db +def test_license_account_status(lic, status): + lic.status = status + lic.save() + assert lic.account_status != '' + if status == consts.STATUS_PAID: + assert 'не' not in lic.account_status + else: + assert 'не' in lic.account_status + + +@freeze_time("2017-06-20 00:21:34", tz_offset=2) +@pytest.mark.django_db +def test_license_account_sub_status_freeze(lic): + lic.status = consts.STATUS_FROZEN + lic.order_date = timezone.now() + lic.save() + assert lic.account_sub_status == 'Счет заморожен 25.06.2017' + + +@freeze_time("2017-06-25 00:21:34", tz_offset=2) +@pytest.mark.parametrize('days', range(1, 6)) +@pytest.mark.django_db +def test_license_account_sub_status_not_paid(lic, days): + lic.status = consts.STATUS_NOT_PAID + lic.order_date = timezone.now().date() - timezone.timedelta(days) + lic.save() + if days != 5: + assert f'{5 - days}' in lic.account_sub_status + else: + assert 'будет заморожен' in lic.account_sub_status + + +@freeze_time("2017-06-25 00:21:34", tz_offset=2) +@pytest.mark.parametrize('days', range(1, 6)) +@pytest.mark.django_db +def test_license_status_need_to_change(lic, days): + lic.status = consts.STATUS_NOT_PAID + lic.order_date = timezone.now().date() - timezone.timedelta(days) + lic.save() + if days > 5: + assert lic.status_need_to_change is True + else: + assert lic.status_need_to_change is False + + +@pytest.mark.django_db +def test_license_set_freeze_status(lic): + lic.set_freeze_status() + assert lic.status is consts.STATUS_FROZEN diff --git a/src/tests/test_tasks.py b/src/tests/test_tasks.py index 00d8e02..f407cb3 100644 --- a/src/tests/test_tasks.py +++ b/src/tests/test_tasks.py @@ -9,8 +9,8 @@ from django.utils import timezone from myauth.models import DokUser, ConfirmEmail from customer.models import UserProfile -from customer.tasks import delete_not_activated_users, send_offer_for_get_bonus - +from customer.tasks import delete_not_activated_users, send_offer_for_get_bonus, \ + check_not_paid_accounts dates_gte_five = [timezone.now() - timezone.timedelta(days=100), timezone.now() - timezone.timedelta(days=15), @@ -88,3 +88,13 @@ def test_send_offer_for_get_bonus_not_range_dates(user, create_date): user.save() users = send_offer_for_get_bonus() assert users == [] + + +@freeze_time("2017-06-28 00:21:34", tz_offset=2) +@pytest.mark.parametrize('days', range(5, 10)) +@pytest.mark.django_db +def test_check_not_paid_accounts(lic, days): + lic.order_date = timezone.now().date() - timezone.timedelta(days) + lic.save() + license_list = check_not_paid_accounts() + assert license_list == [] diff --git a/static/css/style.css b/static/css/style.css index a1806ee..a5af05c 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1014,3 +1014,8 @@ input[type=number] { .modal-upload__bottom--operation-modal { padding-left: 110px; } + +.not-active { + pointer-events: none; + cursor: default; +} diff --git a/templates/base.html b/templates/base.html index 9f720b6..84e41ae 100644 --- a/templates/base.html +++ b/templates/base.html @@ -102,8 +102,9 @@ {{ license_days }} Купить лицензию - Мои заказы - История расчетов + + Лицензии и расчёты
| {{ object.order_date }} | +
+ {% if object.type %}
+ Счет № {{object.id}} - Лицензия на {{ object.get_term }}, + {% if object.pay_sum > 0 %}{{ object.pay_sum|get_plural:"рубль,рубля,рублей" }}{% else %}бесплатно{% endif %} +{% if object.paid_date %}{{ object.get_payform_display }}{% else %}{{ object.get_action_link|safe }}{% endif %} + {% else %} +Лицензия № {{object.id}} на {{ object.get_term }}, + {% if object.pay_sum > 0 %}{{ object.pay_sum|get_plural:"рубль,рубля,рублей" }}{% else %}бесплатно{% endif %} +{% if object.term > 0 %}{{ object.get_action_link|safe }}{% else %}Пробный период{% endif %} + {% endif %} + |
+
+ {% if object.type %}
+ {{ object.account_status }} {% if object.paid_date %}{{ object.paid_date }}{% endif %} +{% if object.paid_date %}Выдана лицензия № {{ object.id }}{% else %}{{ object.account_sub_status }}{% endif %} + {% else %} +{{ object.get_paid_status }} +Период действия: {% if object.date_from %}{{ object.date_from }} - {{ object.date_to }}{% else %}-{% endif %} + {% endif %} + |
+