add task for delete license, add new page license and accounts

prod
Dmitriy Shesterkin 9 years ago
parent e0541b7316
commit 097aa7514f
  1. 20
      src/customer/admin.py
  2. 19
      src/customer/consts.py
  3. 2
      src/customer/context_processors.py
  4. 98
      src/customer/models.py
  5. 21
      src/customer/tasks.py
  6. 3
      src/customer/urls.py
  7. 2
      src/customer/views/documents.py
  8. 25
      src/customer/views/license.py
  9. 4
      src/dokumentor/settings/common.py
  10. 2
      src/factories/models.py
  11. 69
      src/tests/test_models.py
  12. 14
      src/tests/test_tasks.py
  13. 5
      static/css/style.css
  14. 5
      templates/base.html
  15. 35
      templates/customer/profile/orders_list.html

@ -6,12 +6,7 @@ from customer import forms
from customer import models from customer import models
class UserProfileAdmin(admin.ModelAdmin): @admin.register(models.License)
list_display = ('get_email', 'profile_type', 'name', 'inn', 'active')
list_display_links = list_display
form = forms.UserProfileAdminForm
class LicenseAdmin(admin.ModelAdmin): class LicenseAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'user_email', 'user_email',
@ -21,12 +16,14 @@ class LicenseAdmin(admin.ModelAdmin):
'order_date', 'order_date',
'date_from', 'date_from',
'date_to', 'date_to',
'balance_days' 'balance_days',
'deleted'
) )
list_display_links = list_display list_display_links = list_display
search_fields = ('company__email',) 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): def user_email(self, obj):
if obj.company.users.first(): if obj.company.users.first():
@ -56,6 +53,12 @@ class LicenseAdmin(admin.ModelAdmin):
balance_days.short_description = 'Остаток дней' 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 BankAccountAdmin(admin.ModelAdmin):
class Media: class Media:
css = {'all': ('css/custom_admin.css',)} css = {'all': ('css/custom_admin.css',)}
@ -89,5 +92,4 @@ class ClientAdmin(admin.ModelAdmin):
admin.site.register(models.UserProfile, UserProfileAdmin) admin.site.register(models.UserProfile, UserProfileAdmin)
admin.site.register(models.BankAccount, BankAccountAdmin) admin.site.register(models.BankAccount, BankAccountAdmin)
admin.site.register(models.Client, ClientAdmin) admin.site.register(models.Client, ClientAdmin)
admin.site.register(models.License, LicenseAdmin)
admin.site.register(models.LicensePrice) admin.site.register(models.LicensePrice)

@ -8,13 +8,20 @@ PROFILE_TYPES = (
(ORG_PROFILE, 'Организация'), (ORG_PROFILE, 'Организация'),
) )
STATUS_TRAIL = -1
STATUS_NOT_PAID = 0
STATUS_PAID = 1
STATUS_ACTIVATED = 2
STATUS_EXPIRED = 3
STATUS_FROZEN = 4
LICENSE_STATUSES = ( LICENSE_STATUSES = (
(-1, 'Пробный период'), (STATUS_TRAIL, 'Пробный период'),
(0, 'Не оплачен'), (STATUS_NOT_PAID, 'Не оплачен'),
(1, 'Оплачен'), (STATUS_PAID, 'Оплачен'),
(2, 'Активирован'), (STATUS_ACTIVATED, 'Активирован'),
(3, 'Срок действия истёк'), (STATUS_EXPIRED, 'Срок действия истёк'),
(4, 'Заморожен'), (STATUS_FROZEN, 'Заморожен'),
) )
PAYFORMS = ( PAYFORMS = (

@ -15,8 +15,6 @@ def license_check_soon_ends(request):
days_left = cache.get(f'days_left_{request.user.username}', None) days_left = cache.get(f'days_left_{request.user.username}', None)
cur_license = cache.get(f'cur_license_{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: if not days_left or not cur_license:
now = datetime.today() now = datetime.today()
cur_license = License.objects.filter( cur_license = License.objects.filter(

@ -1,6 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils import timezone
from PIL import Image from PIL import Image
from pytils import numeral from pytils import numeral
@ -15,6 +19,8 @@ from myauth.models import DokUser
from commons.utils import only_numerics from commons.utils import only_numerics
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
log = logging.getLogger(__name__)
# куда сохранять загруженные изображения # куда сохранять загруженные изображения
PROFILE_IMAGES_UPLOAD_DIR = 'customer/profile/' PROFILE_IMAGES_UPLOAD_DIR = 'customer/profile/'
@ -365,11 +371,8 @@ class BankAccount(models.Model):
verbose_name_plural = 'Расчётные счета' verbose_name_plural = 'Расчётные счета'
ordering = ['-created_at'] 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): 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): def save(self, *args, **kwargs):
self.bik = only_numerics(self.bik) self.bik = only_numerics(self.bik)
@ -447,17 +450,11 @@ class Client(models.Model):
verbose_name_plural = 'Контрагенты' verbose_name_plural = 'Контрагенты'
ordering = ['name', '-created_at'] 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): def __str__(self):
if self.name_short_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: else:
return ('%s, ИНН %s' % (self.name, self.inn or 'не указан',)).strip() return f'{self.name}, ИНН {self.inn or "не указан"}'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.inn = only_numerics(self.inn) self.inn = only_numerics(self.inn)
@ -473,7 +470,7 @@ class Client(models.Model):
"""Возвращает пару ИНН/КПП или только ИНН, если КПП не заполнен.""" """Возвращает пару ИНН/КПП или только ИНН, если КПП не заполнен."""
kpp = self.kpp.strip() kpp = self.kpp.strip()
if kpp: if kpp:
return '%s/%s' % (self.inn, kpp,) return f'{self.inn}/{kpp}'
return self.inn return self.inn
@ -577,12 +574,13 @@ class License(models.Model):
numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей"), numeral.choose_plural(self.pay_sum, "рубль, рубля, рублей"),
) )
# TODO: test
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.__prev_date and self.paid_date: if not self.__prev_date and self.paid_date:
max_date_license = License.objects.\ max_date_license = License.objects.\
filter(company=self.company).aggregate(Max('date_to'))['date_to__max'] filter(company=self.company).aggregate(Max('date_to'))['date_to__max']
today = datetime.now().date() today = timedelta.now().date()
if max_date_license < today: if max_date_license < today:
max_date_license = today - timedelta(1) max_date_license = today - timedelta(1)
self.date_from = max_date_license + relativedelta(days=1) self.date_from = max_date_license + relativedelta(days=1)
@ -600,16 +598,32 @@ class License(models.Model):
def get_action_link(self): def get_action_link(self):
if self.status == 0: if self.status == 0:
if self.payform == 0: if self.payform == 0:
url = reverse('customer_license_get_doc', kwargs={'order_num': self.id}) url = reverse('customer_license_get_doc', kwargs={'order_num': self.id})
return f'<a href="{url}">Скачать счёт</a>' return f'<a href="{url}">Оплата безналичным платежом</a>'
elif self.payform == 1: elif self.payform == 1:
return 'Оплатить счёт' return f'<a href="#">Оплата банковской картой</a>'
elif self.payform == 2: elif self.payform == 2:
url = reverse('customer_license_get_doc', kwargs={'order_num': self.id}) url = reverse('customer_license_get_doc', kwargs={'order_num': self.id})
return f'<a href="{url}">Скачать квитанцию</a>' return f'<a href="{url}">Скачать квитанцию</a>'
elif self.status in [1, 2]: elif self.status in [1, 2]:
return 'История операций' url = '#'
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: else:
return '' return ''
@ -621,21 +635,65 @@ class License(models.Model):
def get_paid_status(self): def get_paid_status(self):
if self.status == 1: if self.status == 1:
return 'Лицензия оплачена, ещё не активирована' return 'Лицензия выдана, ещё не активирована'
elif self.status in [2, -1]: elif self.status in [2, -1]:
left = relativedelta(self.date_to, datetime.today()) left = relativedelta(self.date_to, timezone.now().date())
if left.months: if left.months:
left_str = f'{left.months} ' \ left_str = f'{left.months} ' \
f'{numeral.choose_plural(left.months, "месяц, месяца, месяцев")} ' \ f'{numeral.choose_plural(left.months, "месяц, месяца, месяцев")} ' \
f'{left.days} {numeral.choose_plural(left.days, "день, дня, дней")}' f'{left.days} {numeral.choose_plural(left.days, "день, дня, дней")}'
else: else:
left_str = f'{left.days} {numeral.choose_plural(left.days, "день, дня, дней")}' 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: elif self.status == 3:
return 'Время истекло' return 'Время истекло'
else: else:
return None 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): class LicensePrice(models.Model):
term = models.IntegerField(verbose_name='срок лицензии', term = models.IntegerField(verbose_name='срок лицензии',

@ -83,3 +83,24 @@ def send_offer_for_get_bonus():
user_list.append(user) user_list.append(user)
emails.send_offer_buy_licence_for_get_bonus.delay(user.email, url) emails.send_offer_buy_licence_for_get_bonus.delay(user.email, url)
return user_list 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

@ -24,8 +24,11 @@ urlpatterns = [
url(r'^payment/result/$', license.payment_result, name='yamoney_result'), url(r'^payment/result/$', license.payment_result, name='yamoney_result'),
url(r'^payment/success/$', license.payment_success, name='yamoney_success'), url(r'^payment/success/$', license.payment_success, name='yamoney_success'),
url(r'^payment/fail/$', license.payment_fail, name='yamoney_fail'), 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'^license_list/$', license.license_list, name='customer_license_list'),
url(r'^paid_list/$', license.paid_list, name='customer_paid_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 # --- профиль AJAX
url(r'^profile/filters/edit/ajax/$', profile_ajax.profile_filters_edit_ajax, url(r'^profile/filters/edit/ajax/$', profile_ajax.profile_filters_edit_ajax,

@ -28,7 +28,7 @@ def get_doc(request, order_num=None):
data = request.user.profile data = request.user.profile
if pm == 0: if pm == 0:
tmp_name = 'bill.xls' tmp_name = 'bill.xls'
file_name = "Invoice No.{order_num}.xls" file_name = f"Invoice No.{order_num}.xls"
elif pm == 2: elif pm == 2:
tmp_name = '4pd.xls' tmp_name = '4pd.xls'
file_name = f"Kvitanciya na oplatu zakaza No.{order_num}.xls" file_name = f"Kvitanciya na oplatu zakaza No.{order_num}.xls"

@ -2,6 +2,9 @@
import json import json
import hashlib import hashlib
import itertools
from django.db.models import Count
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.http import (HttpResponseForbidden, HttpResponse, HttpResponseBadRequest) from django.http import (HttpResponseForbidden, HttpResponse, HttpResponseBadRequest)
from django.conf import settings from django.conf import settings
@ -163,3 +166,25 @@ def payment_fail(request):
{'message': message, 'success': False}) {'message': message, 'success': False})
except: except:
return HttpResponseForbidden() 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)

@ -278,6 +278,10 @@ CELERYBEAT_SCHEDULE = {
'task': 'src.customer.tasks.send_offer_for_get_bonus', 'task': 'src.customer.tasks.send_offer_for_get_bonus',
'schedule': timedelta(days=1), 'schedule': timedelta(days=1),
}, },
'check_not_paid_accounts': {
'task': 'src.customer.tasks.check_not_paid_accounts',
'schedule': timedelta(days=1),
},
} }
CAPTCHA_OUTPUT_FORMAT = \ CAPTCHA_OUTPUT_FORMAT = \

@ -101,7 +101,7 @@ class LicenseFactory(factory.django.DjangoModelFactory):
start_date='-30d', start_date='-30d',
tzinfo=pytz.UTC tzinfo=pytz.UTC
) )
pay_sum = factory.Iterator([1000, 2000]) pay_sum = factory.Iterator([100, 200, 1000, 2000])
deleted = False deleted = False
class Meta: class Meta:

@ -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

@ -9,8 +9,8 @@ from django.utils import timezone
from myauth.models import DokUser, ConfirmEmail from myauth.models import DokUser, ConfirmEmail
from customer.models import UserProfile 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), dates_gte_five = [timezone.now() - timezone.timedelta(days=100),
timezone.now() - timezone.timedelta(days=15), 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() user.save()
users = send_offer_for_get_bonus() users = send_offer_for_get_bonus()
assert users == [] 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 == []

@ -1014,3 +1014,8 @@ input[type=number] {
.modal-upload__bottom--operation-modal { .modal-upload__bottom--operation-modal {
padding-left: 110px; padding-left: 110px;
} }
.not-active {
pointer-events: none;
cursor: default;
}

@ -102,8 +102,9 @@
<span class='yellow_round'>{{ license_days }}</span> <span class='yellow_round'>{{ license_days }}</span>
</div> </div>
<a href='{% url "customer_order_license" %}' class='popup-link popup-buy-license'>Купить лицензию</a> <a href='{% url "customer_order_license" %}' class='popup-link popup-buy-license'>Купить лицензию</a>
<a href='{% url "customer_license_list" %}' class='popup-link popup-my-accs'>Мои заказы</a> <!--a href='{% url "customer_license_list" %}' class='popup-link popup-my-accs'>Мои заказы</a>
<a href='{% url "customer_paid_list" %}' class='popup-link popup-history'>История расчетов</a> <a href='{% url "customer_paid_list" %}' class='popup-link popup-history'>История расчетов</a-->
<a href='{% url "customer-orders" %}' class='popup-link popup-my-accs'>Лицензии и расчёты</a>
<h2>Настройки</h2> <h2>Настройки</h2>
<a href='{% url "myauth_change_password" %}' class='popup-link popup-password'>Сменить пароль</a> <a href='{% url "myauth_change_password" %}' class='popup-link popup-password'>Сменить пароль</a>
<a href='{% url "myauth_change_email" %}' class='popup-link popup-email'>Сменить e-mail</a> <a href='{% url "myauth_change_email" %}' class='popup-link popup-email'>Сменить e-mail</a>

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% load pytils_numeral %}
{% block title %}Лицензии и расчёты{% endblock %}
{% block content %}
<h1>Лицензии и расчёты</h1>
<div class="btn yellow-btn docs-btn"><a href="{% url 'customer_order_license' %}">Купить лицензию</a></div>
<table id="history" class="list">
{% for object in object_list %}
<tr class='{% if object.type %}account_{% else %}license_{% endif %}{{ object.id }}'>
<td>{{ object.order_date }}</td>
<td>
{% if object.type %}
<p>Счет № {{object.id}} - Лицензия на {{ object.get_term }},
{% if object.pay_sum > 0 %}{{ object.pay_sum|get_plural:"рубль,рубля,рублей" }}{% else %}бесплатно{% endif %}</p>
<p>{% if object.paid_date %}{{ object.get_payform_display }}{% else %}{{ object.get_action_link|safe }}{% endif %}</p>
{% else %}
<p>Лицензия № {{object.id}} на {{ object.get_term }},
{% if object.pay_sum > 0 %}{{ object.pay_sum|get_plural:"рубль,рубля,рублей" }}{% else %}бесплатно{% endif %}</p>
<p>{% if object.term > 0 %}{{ object.get_action_link|safe }}{% else %}Пробный период{% endif %}</p>
{% endif %}
</td>
<td>
{% if object.type %}
<p>{{ object.account_status }} {% if object.paid_date %}{{ object.paid_date }}{% endif %}</p>
<p>{% if object.paid_date %}Выдана лицензия № {{ object.id }}{% else %}{{ object.account_sub_status }}{% endif %}</p>
{% else %}
<p>{{ object.get_paid_status }}</p>
<p>Период действия: {% if object.date_from %}{{ object.date_from }} - {{ object.date_to }}{% else %}-{% endif %}</p>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endblock content %}
Loading…
Cancel
Save