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

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

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

@ -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'<a href="{url}">Скачать счёт</a>'
return f'<a href="{url}">Оплата безналичным платежом</a>'
elif self.payform == 1:
return 'Оплатить счёт'
return f'<a href="#">Оплата банковской картой</a>'
elif self.payform == 2:
url = reverse('customer_license_get_doc', kwargs={'order_num': self.id})
return f'<a href="{url}">Скачать квитанцию</a>'
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:
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='срок лицензии',

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

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

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

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

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

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

@ -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 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 == []

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

@ -102,8 +102,9 @@
<span class='yellow_round'>{{ license_days }}</span>
</div>
<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_paid_list" %}' class='popup-link popup-history'>История расчетов</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-orders" %}' class='popup-link popup-my-accs'>Лицензии и расчёты</a>
<h2>Настройки</h2>
<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>

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