Browse Source

1) Maked periodic pays.

2) Settings reformat.
3) more logs
devel
bashmak 8 years ago
parent
commit
62c3aa8118
  1. 2
      .gitignore
  2. 3
      README.md
  3. 6
      abonapp/forms.py
  4. 483
      abonapp/locale/ru/LC_MESSAGES/django.po
  5. 130
      abonapp/migrations/0004_auto_20180122_1732.py
  6. 65
      abonapp/models.py
  7. 11
      abonapp/pay_systems.py
  8. 36
      abonapp/templates/abonapp/modal_periodic_pay.html
  9. 20
      abonapp/templates/abonapp/payHistory.html
  10. 35
      abonapp/templates/abonapp/service.html
  11. 6
      abonapp/urls_abon.py
  12. 59
      abonapp/views.py
  13. 8
      cron.py
  14. 52
      djing/local_settings.py.template
  15. 59
      djing/settings.py
  16. 1
      requirements.txt
  17. 44
      tariff_app/base_intr.py
  18. 42
      tariff_app/custom_tariffs.py
  19. 14
      tariff_app/forms.py
  20. 69
      tariff_app/migrations/0006_auto_20180122_1732.py
  21. 94
      tariff_app/models.py
  22. 67
      tariff_app/templates/tariff_app/editTarif.html
  23. 36
      tariff_app/templates/tariff_app/ext.html
  24. 51
      tariff_app/templates/tariff_app/periodic_pays/add_edit.html
  25. 62
      tariff_app/templates/tariff_app/periodic_pays/list.html
  26. 12
      tariff_app/templates/tariff_app/tarifs.html
  27. 6
      tariff_app/urls.py
  28. 45
      tariff_app/views.py
  29. 6
      taskapp/templates/taskapp/tasklist.html
  30. 4
      taskapp/templates/taskapp/tasklist_all.html
  31. 4
      taskapp/templates/taskapp/tasklist_failed.html
  32. 4
      taskapp/templates/taskapp/tasklist_finish.html
  33. 4
      taskapp/templates/taskapp/tasklist_own.html

2
.gitignore

@ -5,7 +5,7 @@ media/*
media/min/*
~*/migrations/00*.py
.idea/
djing/settings.py
djing/local_settings.py
agent/settings.py
gmap/fixtures
*.sqlite3

3
README.md

@ -3,6 +3,9 @@
Сейчас идёт тестирвоание работы на Mikrotik, функционал пока минимальный, т.к. пишу в свободное время. Работа планируется в реальных условиях и на реальных абонентах.
Использовано python 3, django 1.11, bootstrap 3, и другое в файле requirements.txt
P.S. Возможно понадобится **Python 3.5** и выше из-за указания статических типов. [typing — Support for type hints](https://docs.python.org/3/library/typing.html).
Вы можете использовать билиотеку [mypy](http://www.mypy-lang.org/) для старших версий python.
## Содержание
* [Установка](./docs/install.md)
* [Разработка расширений](./docs/dev.md)

6
abonapp/forms.py

@ -150,3 +150,9 @@ class AdditionalTelephoneForm(forms.ModelForm):
}),
'owner_name': forms.TextInput(attrs={'class': 'form-control', 'required':''})
}
class PeriodicPayForIdForm(forms.ModelForm):
class Meta:
model = models.PeriodicPayForId
exclude = ['account']

483
abonapp/locale/ru/LC_MESSAGES/django.po

@ -6,10 +6,8 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-12-27 14:08+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Report-Msgid-Bugs-To: nerosketch@gmail.com\n"
"POT-Creation-Date: 2018-01-22 17:58+0300\n"
"Last-Translator: Dmitry Novikov nerosketch@gmail.com\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
@ -19,7 +17,13 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: templates/abonapp/peoples.html:38 templates/abonapp/viewAbon.html.py:32
#: forms.py:45 templates/abonapp/addAbon.html:21
#: templates/abonapp/viewAbon.html:28
msgid "login"
msgstr "Логин"
#: forms.py:58 templates/abonapp/peoples.html:38
#: templates/abonapp/viewAbon.html:32
msgid "fio"
msgstr "ФИО"
@ -27,15 +31,15 @@ msgstr "ФИО"
msgid "telephone placeholder"
msgstr "+[7,8,9,3] и 10,11 цифр"
#: models.py:30
#: models.py:29
msgid "Can view subscriber group"
msgstr "Может просматривать группу абонентов"
#: models.py:32
#: models.py:31
msgid "Abon group"
msgstr "Группа абонентов"
#: models.py:33
#: models.py:32
msgid "Abon groups"
msgstr "Группы абонентов"
@ -43,150 +47,194 @@ msgstr "Группы абонентов"
msgid "Can view subscriber logs"
msgstr "Может видеть логи абонента"
#: models.py:82
#: models.py:83
msgid "finish service perm"
msgstr "Снятие со счёта средств"
#: models.py:84
#: models.py:85
msgid "Abon service"
msgstr "Услуга абонента"
#: models.py:85
#: models.py:86
msgid "Abon services"
msgstr "Услуги абонентов"
#: models.py:97 templates/abonapp/addAbon.html.py:59
#: templates/abonapp/editAbon.html:64 templates/abonapp/peoples.html.py:44
#: templates/abonapp/viewAbon.html:40
#: models.py:99 models.py:159 templates/abonapp/addAbon.html:59
#: templates/abonapp/peoples.html:44 templates/abonapp/viewAbon.html:40
msgid "Street"
msgstr "Улица"
#: models.py:98 templates/abonapp/peoples.html.py:148
#: models.py:100 templates/abonapp/peoples.html:148
msgid "Streets"
msgstr "Улицы"
#: models.py:103
#: models.py:106
msgid "Digital field"
msgstr "Цифровое поле"
#: models.py:104
#: models.py:107
msgid "Text field"
msgstr "Текстовое поле"
#: models.py:105
#: models.py:108
msgid "Floating field"
msgstr "Дробное с плавающей точкой"
#: models.py:106 templates/abonapp/editAbon.html.py:50
#: models.py:109 templates/abonapp/editAbon.html:39
#: templates/abonapp/viewAbon.html:50
msgid "Ip Address"
msgstr "IP Адрес"
#: models.py:131
#: models.py:134
msgid "Double invalid value"
msgstr "Введите число с плавающей запятой"
#: models.py:172
#: models.py:155 templates/abonapp/addAbon.html:45
#: templates/abonapp/modal_addstreet.html:21 templates/abonapp/viewAbon.html:16
msgid "User group"
msgstr "Группа"
#: models.py:158 templates/abonapp/addAbon.html:53
#: templates/abonapp/addInvoice.html:40 templates/abonapp/debtors.html:22
#: templates/abonapp/invoiceForPayment.html:23 templates/abonapp/log.html:20
#: templates/abonapp/payHistory.html:13 templates/abonapp/task_log.html:10
msgid "Comment"
msgstr "Комментарий"
#: models.py:160 templates/abonapp/viewAbon.html:46
msgid "House"
msgstr "Дом"
#: models.py:175
msgid "Buy service perm"
msgstr "Покупка тарифа абоненту"
#: models.py:173
#: models.py:176
msgid "Can view passport"
msgstr "Может просматривать паспортные данные"
#: models.py:174
#: models.py:177
msgid "fill account"
msgstr "Пополнение счёта"
#: models.py:175
#: models.py:178
msgid "Can ping"
msgstr "Может пинговать"
#: models.py:178
#: models.py:182
msgid "Abon"
msgstr "Абонент"
#: models.py:179
#: models.py:183
msgid "Abons"
msgstr "Абоненты"
#: models.py:204
#: models.py:211
msgid "User that is no staff can not buy admin services"
msgstr ""
"Пользователь, который не является персоналом не может покупать услуги для "
"внутренних нужд"
#: models.py:209
#: models.py:216
msgid "That service already activated"
msgstr "Эта услуга уже подключена"
#: models.py:212
#: models.py:219
msgid "Service already activated"
msgstr "Услуга уже подключена"
#: models.py:216
#: models.py:223
msgid "not enough money"
msgstr "Не хватает денег на счету"
#: models.py:231
#: models.py:238
msgid "Buy service default log"
msgstr "Покупка тарифного плана через админку"
#: models.py:272
#: models.py:279
msgid "Ip address already exist"
msgstr "Такой ip уже у кого-то есть"
#: models.py:285 models.py:286
#: models.py:292 models.py:293
msgid "Passport Info"
msgstr "Паспортные данные"
#: models.py:315
#: models.py:322
msgid "Can view invoice for payment"
msgstr "Может видеть назначенные платежи"
#: models.py:317
#: models.py:324
msgid "Debt"
msgstr "Квитанция (долг)"
#: models.py:318 templates/abonapp/invoiceForPayment.html.py:10
#: templates/abonapp/payHistory.html:44
#: models.py:325 templates/abonapp/invoiceForPayment.html:10
#: templates/abonapp/payHistory.html:50
msgid "Debts"
msgstr "Квитанции (долги)"
#: models.py:380 templates/abonapp/addAbon.html.py:37
#: templates/abonapp/editAbon.html:30 templates/abonapp/modal_add_phone.html:10
#: models.py:348
msgid "Trade point"
msgstr "Терминал"
#: models.py:349
msgid "Receipt number"
msgstr "Номер пас."
#: models.py:391 templates/abonapp/addAbon.html:37
#: templates/abonapp/editAbon.html:19 templates/abonapp/modal_add_phone.html:10
#: templates/abonapp/modal_additional_telephones.html:14
#: templates/abonapp/modal_phonebook.html:10 templates/abonapp/peoples.html:54
#: templates/abonapp/viewAbon.html.py:36
#: templates/abonapp/viewAbon.html:36
msgid "Telephone"
msgstr "Телефон"
#: models.py:393
#: models.py:404
msgid "Can view additional telephones"
msgstr "Может видеть дополнительные телефоны"
#: models.py:395
#: models.py:406
msgid "Additional telephone"
msgstr "Дополнительный телефон"
#: models.py:396 templates/abonapp/editAbon.html.py:38
#: models.py:407 templates/abonapp/editAbon.html:27
#: templates/abonapp/modal_additional_telephones.html:5
msgid "Additional telephones"
msgstr "Дополнительные телефоны"
#: templates/abonapp/addAbon.html:7 templates/abonapp/addGroup.html.py:7
#: models.py:411 templates/abonapp/service.html:122
msgid "Periodic pay"
msgstr "Периодический платёж"
#: models.py:412
msgid "Last pay time"
msgstr "Последний платёж"
#: models.py:413
msgid "Next time to pay"
msgstr "Следующий платёж"
#: models.py:414
msgid "Account"
msgstr "Учётная запись"
#: models.py:432
#, python-format
msgid "Charge for \"%(service)s\""
msgstr "Плата за \"%(service)s\""
#: templates/abonapp/addAbon.html:7 templates/abonapp/addGroup.html:7
#: templates/abonapp/addInvoice.html:7 templates/abonapp/buy_tariff.html:7
#: templates/abonapp/debtors.html.py:8 templates/abonapp/group_list.html:8
#: templates/abonapp/debtors.html:8 templates/abonapp/group_list.html:8
#: templates/abonapp/group_list.html:11 templates/abonapp/group_list.html:47
#: templates/abonapp/group_tariffs.html:7
#: templates/abonapp/invoiceForPayment.html:7 templates/abonapp/log.html:7
#: templates/abonapp/peoples.html.py:9 templates/abonapp/peoples.html:134
#: templates/abonapp/service.html.py:103 templates/abonapp/service.html:111
#: templates/abonapp/peoples.html:9 templates/abonapp/peoples.html:134
#: templates/abonapp/service.html:103 templates/abonapp/service.html:111
msgid "User groups"
msgstr "Группы абонентов"
#: templates/abonapp/addAbon.html:9 templates/abonapp/addAbon.html.py:16
#: templates/abonapp/peoples.html:119 templates/abonapp/peoples.html.py:131
#: templates/abonapp/addAbon.html:9 templates/abonapp/addAbon.html:16
#: templates/abonapp/peoples.html:119 templates/abonapp/peoples.html:131
msgid "Add abon"
msgstr "Добавить абонента"
@ -194,38 +242,26 @@ msgstr "Добавить абонента"
msgid "Long name"
msgstr "Фамилия и Имя"
#: templates/abonapp/addAbon.html:45 templates/abonapp/editAbon.html.py:87
#: templates/abonapp/modal_addstreet.html:21 templates/abonapp/viewAbon.html:16
msgid "User group"
msgstr "Группа"
#: templates/abonapp/addAbon.html:53 templates/abonapp/addInvoice.html.py:40
#: templates/abonapp/debtors.html:22 templates/abonapp/editAbon.html.py:108
#: templates/abonapp/invoiceForPayment.html:23 templates/abonapp/log.html:20
#: templates/abonapp/payHistory.html.py:13 templates/abonapp/task_log.html:10
msgid "Comment"
msgstr "Комментарий"
#: templates/abonapp/addAbon.html:67 templates/abonapp/peoples.html.py:50
#: templates/abonapp/addAbon.html:67 templates/abonapp/peoples.html:50
msgid "Apartment"
msgstr "Квартира"
#: templates/abonapp/addAbon.html:76 templates/abonapp/editAbon.html.py:94
#: templates/abonapp/addAbon.html:76 templates/abonapp/editAbon.html:58
#: templates/abonapp/viewAbon.html:54
msgid "Password"
msgstr "Пароль"
#: templates/abonapp/addAbon.html:91 templates/abonapp/addGroup.html.py:29
#: templates/abonapp/addAbon.html:91 templates/abonapp/addGroup.html:29
#: templates/abonapp/addInvoice.html:46 templates/abonapp/buy_tariff.html:71
#: templates/abonapp/editAbon.html:117 templates/abonapp/editAbon.html:197
#: templates/abonapp/editAbon.html:242 templates/abonapp/group_tariffs.html:29
#: templates/abonapp/editAbon.html:76 templates/abonapp/editAbon.html:165
#: templates/abonapp/editAbon.html:210 templates/abonapp/group_tariffs.html:29
#: templates/abonapp/modal_dev.html:31
#: templates/abonapp/modal_editstreet.html:30
#: templates/abonapp/passport_view.html:49
msgid "Save"
msgstr "Сохранить"
#: templates/abonapp/addAbon.html:94 templates/abonapp/addGroup.html.py:32
#: templates/abonapp/addAbon.html:94 templates/abonapp/addGroup.html:32
#: templates/abonapp/addInvoice.html:49 templates/abonapp/buy_tariff.html:74
#: templates/abonapp/group_tariffs.html:29
#: templates/abonapp/modal_abonamount.html:25
@ -234,10 +270,11 @@ msgstr "Сохранить"
#: templates/abonapp/modal_dev.html:34
#: templates/abonapp/modal_editstreet.html:33
#: templates/abonapp/modal_extra_field.html:40
#: templates/abonapp/modal_periodic_pay.html:30
msgid "Reset"
msgstr "Сбросить"
#: templates/abonapp/addGroup.html:8 templates/abonapp/addGroup.html.py:15
#: templates/abonapp/addGroup.html:8 templates/abonapp/addGroup.html:15
#: templates/abonapp/group_list.html:63
msgid "Add group"
msgstr "Добавьте группу абонентов"
@ -255,7 +292,7 @@ msgstr "Добавить квитанцию"
msgid "Add receipt for"
msgstr "Добавьте платёж на оплату для"
#: templates/abonapp/addInvoice.html:25 templates/abonapp/debtors.html.py:21
#: templates/abonapp/addInvoice.html:25 templates/abonapp/debtors.html:21
msgid "Sum of pay"
msgstr "Сумма для платежа"
@ -265,7 +302,7 @@ msgid "Pay status"
msgstr "Статус оплаты"
#: templates/abonapp/buy_tariff.html:10 templates/abonapp/buy_tariff.html:26
#: templates/abonapp/service.html.py:74
#: templates/abonapp/service.html:74
msgid "Pick a service"
msgstr "Заказать услугу"
@ -273,14 +310,14 @@ msgstr "Заказать услугу"
msgid "Pick a service for"
msgstr "Купить новую услугу (заказать тариф) для"
#: templates/abonapp/buy_tariff.html:18 templates/abonapp/debtors.html.py:20
#: templates/abonapp/log.html:19 templates/abonapp/payHistory.html.py:9
#: templates/abonapp/buy_tariff.html:18 templates/abonapp/debtors.html:20
#: templates/abonapp/log.html:19 templates/abonapp/payHistory.html:9
#: templates/abonapp/peoples.html:25
msgid "Sub"
msgstr "Абонент"
#: templates/abonapp/buy_tariff.html:36 templates/abonapp/group_tariffs.html:24
#: templates/abonapp/service.html:31 templates/abonapp/service.html.py:96
#: templates/abonapp/service.html:31 templates/abonapp/service.html:96
msgid "currency"
msgstr "руб"
@ -293,10 +330,19 @@ msgid "Attach serices to groups"
msgstr "Привязать услуги к группам"
#: templates/abonapp/charts.html:9
msgid "Graph of use"
msgstr "График использования"
#, python-format
msgid "Graph of use by %(wantdate_d)s"
msgstr "График использования за %(wantdate_d)s"
#: templates/abonapp/charts.html:17
msgid "Show graph by date"
msgstr "Показать график по дате"
#: templates/abonapp/charts.html:20
msgid "Choose a date"
msgstr "Выберите дату"
#: templates/abonapp/charts.html:44 templates/abonapp/charts.html.py:56
#: templates/abonapp/charts.html:55
msgid "Static info was Not found"
msgstr "Статистика не найдена"
@ -314,7 +360,7 @@ msgstr "Народ, у которого есть неоплаченные усл
msgid "Date of make"
msgstr "Дата создания"
#: templates/abonapp/debtors.html:24 templates/abonapp/log.html.py:22
#: templates/abonapp/debtors.html:24 templates/abonapp/log.html:22
#: templates/abonapp/task_log.html:8
msgid "Author"
msgstr "Автор"
@ -367,89 +413,92 @@ msgstr "Звонки не найдены"
msgid "Change subscriber"
msgstr "Изменение абонента"
#: templates/abonapp/editAbon.html:35
#: templates/abonapp/editAbon.html:24
msgid "Call to"
msgstr "Позвонить"
#: templates/abonapp/editAbon.html:41 templates/abonapp/modal_add_phone.html:5
#: templates/abonapp/editAbon.html:30 templates/abonapp/modal_add_phone.html:5
msgid "Add telephone"
msgstr "Добавить номер телефона"
#: templates/abonapp/editAbon.html:53 templates/abonapp/editAbon.html.py:155
#: templates/abonapp/editAbon.html:174 templates/abonapp/editAbon.html:220
#: templates/abonapp/peoples.html.py:90 templates/abonapp/peoples.html:92
#: templates/abonapp/viewAbon.html.py:18 templates/abonapp/viewAbon.html:29
#: templates/abonapp/viewAbon.html.py:33 templates/abonapp/viewAbon.html:37
#: templates/abonapp/viewAbon.html.py:42 templates/abonapp/viewAbon.html:47
#: templates/abonapp/viewAbon.html.py:51
#: templates/abonapp/editAbon.html:42 templates/abonapp/editAbon.html:125
#: templates/abonapp/editAbon.html:144 templates/abonapp/editAbon.html:188
#: templates/abonapp/peoples.html:90 templates/abonapp/peoples.html:92
#: templates/abonapp/viewAbon.html:18 templates/abonapp/viewAbon.html:29
#: templates/abonapp/viewAbon.html:33 templates/abonapp/viewAbon.html:37
#: templates/abonapp/viewAbon.html:42 templates/abonapp/viewAbon.html:47
#: templates/abonapp/viewAbon.html:51
msgid "Not assigned"
msgstr "&lt;Не назначен&gt;"
#: templates/abonapp/editAbon.html:55
#: templates/abonapp/editAbon.html:44
msgid "Reset ip"
msgstr "Сбросить ip"
#: templates/abonapp/editAbon.html:72 templates/abonapp/viewAbon.html.py:46
msgid "House"
msgstr "Дом"
#: templates/abonapp/editAbon.html:120 templates/abonapp/editAbon.html:121
#, fuzzy
#| msgid "Add debt"
#: templates/abonapp/editAbon.html:80 templates/abonapp/editAbon.html:81
msgid "Add new task"
msgstr "Добавить квитанцию"
msgstr "Добавить задачу"
#: templates/abonapp/editAbon.html:145 templates/abonapp/modal_dev.html:13
#: templates/abonapp/editAbon.html:95
msgid "No have ip"
msgstr "Нет ip адреса"
#: templates/abonapp/editAbon.html:101
msgid "Send sms"
msgstr "Отправить смс"
#: templates/abonapp/editAbon.html:115 templates/abonapp/modal_dev.html:13
msgid "Select the device"
msgstr "Выберите устройство"
#: templates/abonapp/editAbon.html:152
#: templates/abonapp/editAbon.html:122
msgid "Device"
msgstr "Устройство"
#: templates/abonapp/editAbon.html:155
#: templates/abonapp/editAbon.html:125
msgid "Mac Address"
msgstr "Мак"
#: templates/abonapp/editAbon.html:159
#: templates/abonapp/editAbon.html:129
msgid "Remove clutch"
msgstr "Удалить муфту"
#: templates/abonapp/editAbon.html:163 templates/abonapp/modal_dev.html.py:6
#: templates/abonapp/editAbon.html:133 templates/abonapp/modal_dev.html:6
msgid "Add clutch"
msgstr "Добавить муфту"
#: templates/abonapp/editAbon.html:171
#: templates/abonapp/editAbon.html:141
msgid "Device port"
msgstr "Порт&nbsp;устройства"
#: templates/abonapp/editAbon.html:190
#: templates/abonapp/editAbon.html:158
msgid "Is dynamic network settings"
msgstr "Динамические настройки по dhcp"
#: templates/abonapp/editAbon.html:209
#: templates/abonapp/editAbon.html:177
msgid "Extra fields"
msgstr "Динамические записи"
#: templates/abonapp/editAbon.html:223
#: templates/abonapp/editAbon.html:191
#: templates/abonapp/modal_additional_telephones.html:24
msgid "Delete"
msgstr "Удалить"
#: templates/abonapp/editAbon.html:232 views.py:671
#: templates/abonapp/editAbon.html:200 views.py:681
msgid "Extra field does not exist"
msgstr "Поле не найдено"
#: templates/abonapp/editAbon.html:237
#: templates/abonapp/editAbon.html:205
#: templates/abonapp/modal_extra_field.html:6
msgid "Add extra field"
msgstr "Добавить новое динамическое поле"
#: templates/abonapp/editAbon.html:238
#: templates/abonapp/editAbon.html:206
#: templates/abonapp/modal_add_phone.html:27
#: templates/abonapp/modal_addstreet.html:30
#: templates/abonapp/modal_extra_field.html:37
#: templates/abonapp/peoples.html:130 templates/abonapp/peoples.html.py:157
#: templates/abonapp/modal_periodic_pay.html:27
#: templates/abonapp/peoples.html:130 templates/abonapp/peoples.html:157
msgid "Add"
msgstr "Добавить"
@ -463,19 +512,22 @@ msgstr "Сумма денег за сутки"
#: templates/abonapp/fin_report.html:17
#: templates/abonapp/invoiceForPayment.html:22 templates/abonapp/log.html:18
#: templates/abonapp/payHistory.html.py:10 templates/abonapp/service.html:30
#: templates/abonapp/payHistory.html:10 templates/abonapp/service.html:30
msgid "Sum"
msgstr "Сумма"
#: templates/abonapp/fin_report.html:18 templates/abonapp/log.html.py:21
#: templates/abonapp/fin_report.html:18 templates/abonapp/log.html:21
msgid "Date"
msgstr "Время"
#: templates/abonapp/fin_report.html:29
#, fuzzy
#| msgid "Tasks not found"
msgid "Pays not found"
msgstr "Нет задач"
msgstr "Нет платежей"
#: templates/abonapp/fin_report.html:38
#: templates/abonapp/modal_phonebook.html:30
msgid "Export to csv"
msgstr "Сохранить в csv"
#: templates/abonapp/group_list.html:27
msgid "Number of subscribers"
@ -485,7 +537,7 @@ msgstr "Количество абонентов"
msgid "Groups was not found"
msgstr "Ещё нет групп"
#: templates/abonapp/group_list.html:68 templates/abonapp/log.html.py:8
#: templates/abonapp/group_list.html:68 templates/abonapp/log.html:8
msgid "Subscribers actions"
msgstr "История действий абонентов"
@ -582,19 +634,18 @@ msgstr "Тип динамического поля"
msgid "Field content"
msgstr "Содержимое динамического поля"
#: templates/abonapp/modal_periodic_pay.html:5
#: templates/abonapp/service.html:139
msgid "Add periodic pay"
msgstr "Добавить периодический платёж"
#: templates/abonapp/modal_phonebook.html:4 templates/abonapp/peoples.html:138
msgid "Phonebook"
msgstr "Телефонная книга"
#: templates/abonapp/modal_phonebook.html:22
#, fuzzy
#| msgid "Telephone not found"
msgid "Telephone numbers not found"
msgstr "Телефон не найден"
#: templates/abonapp/modal_phonebook.html:30
msgid "Export to csv"
msgstr "Сохранить в csv"
msgstr "Номера телефонов не найдены"
#: templates/abonapp/passport_view.html:9 templates/abonapp/viewAbon.html:68
msgid "Passport information"
@ -616,15 +667,19 @@ msgstr "Кем выдан"
msgid "Date of acceptance"
msgstr "Дата выдачи"
#: templates/abonapp/payHistory.html:27
#: templates/abonapp/payHistory.html:26
msgid "System"
msgstr "Система"
#: templates/abonapp/payHistory.html:33
msgid "Payment history is empty"
msgstr "История платежей пуста"
#: templates/abonapp/payHistory.html:36 templates/abonapp/payHistory.html:40
#: templates/abonapp/payHistory.html:42 templates/abonapp/payHistory.html:46
msgid "Fill account"
msgstr "Пополнить счёт"
#: templates/abonapp/payHistory.html:39
#: templates/abonapp/payHistory.html:45
msgid "Permission denied"
msgstr "Доступ запрещён"
@ -637,12 +692,10 @@ msgid "Last traffic"
msgstr "Траф."
#: templates/abonapp/peoples.html:32
#, fuzzy
#| msgid "Ip Address"
msgid "Ip address"
msgstr "IP Адрес"
#: templates/abonapp/peoples.html:55 templates/abonapp/service.html.py:16
#: templates/abonapp/peoples.html:55 templates/abonapp/service.html:16
#: templates/abonapp/service.html:75
msgid "Service"
msgstr "Услуга"
@ -655,7 +708,7 @@ msgstr "Балланс"
msgid "Subscribers not found"
msgstr "Абоненты не найдены"
#: templates/abonapp/peoples.html:135 templates/abonapp/service.html.py:104
#: templates/abonapp/peoples.html:135 templates/abonapp/service.html:104
msgid "Tariffs in groups"
msgstr "Тарифы в группах"
@ -703,6 +756,10 @@ msgstr "Купить услугу"
msgid "Finish service"
msgstr "Завершить услугу"
#: templates/abonapp/service.html:68
msgid "Services for buy"
msgstr "Услуги для заказа"
#: templates/abonapp/service.html:76
msgid "Price"
msgstr "Сумма"
@ -719,6 +776,18 @@ msgstr "Исходящая скорость"
msgid "Attach services to group"
msgstr "Привязать услуги к этой группе"
#: templates/abonapp/service.html:127
msgid "Pay logic"
msgstr "Алгоритм платежа"
#: templates/abonapp/service.html:129
msgid "Last pay"
msgstr "Последний платёж"
#: templates/abonapp/service.html:135
msgid "Remove periodic pay"
msgstr "Удалить периодический платёж"
#: templates/abonapp/task_log.html:9
msgid "Recipients"
msgstr "Исполнители"
@ -743,6 +812,10 @@ msgstr "Нет задач"
msgid "View the subscriber"
msgstr "Просмотр абонента"
#: templates/abonapp/viewAbon.html:22
msgid "Is active"
msgstr "Активен"
#: templates/abonapp/viewAbon.html:24
msgid "yes,no"
msgstr "Да,Нет"
@ -751,8 +824,8 @@ msgstr "Да,Нет"
msgid "create group success msg"
msgstr "Группа успешно создана"
#: views.py:86 views.py:158 views.py:295 views.py:407 views.py:485 views.py:627
#: views.py:778 views.py:850
#: views.py:86 views.py:158 views.py:305 views.py:417 views.py:495 views.py:637
#: views.py:800 views.py:872
msgid "fix form errors"
msgstr "Некоторые поля заполнены не правильно, проверте ещё раз"
@ -790,181 +863,204 @@ msgstr "Счёт пополнен на %.2f"
msgid "I not know the account id"
msgstr "Счёт успешно пополнен на %.2f"
#: views.py:293
#: views.py:267
msgid "User group id is not matches with group in url"
msgstr "Группа абонента не совпадает с группой указанной в url"
#: views.py:303
msgid "edit abon success msg"
msgstr "Абонент успешно изменён"
#: views.py:300
#: views.py:310
msgid "User device was not found"
msgstr "Пользовательское устройство не найдено"
#: views.py:309
#: views.py:319
msgid "User has not have password, and cannot login"
msgstr "Для абонента не задан пароль, он не сможет войти в учётку"
#: views.py:362
#: views.py:372
msgid "Receipt has been created"
msgstr "Квитанция на оплату была создана"
#: views.py:394
#: views.py:404
msgid "Tariff has been picked"
msgstr "Тариф успешно выбран"
#: views.py:402
#: views.py:412
msgid "Tariff your picked does not exist"
msgstr "Тариф, который вы выбрали, не существует"
#: views.py:424
#: views.py:434
msgid "User has been detached from service"
msgstr "Абонент отвязан от услуги"
#: views.py:482
#: views.py:492
msgid "Passport information has been saved"
msgstr "Информация о паспорте сохранена"
#: views.py:490 views.py:537 views.py:556 views.py:597
#: views.py:500 views.py:547 views.py:566 views.py:607
msgid "Abon does not exist"
msgstr "Абонент не найден"
#: views.py:493
#: views.py:503
msgid "Passport info for the user does not exist"
msgstr "Для абонента не найдены паспортные данные"
#: views.py:530
#: views.py:540
msgid "Device has successfully attached"
msgstr "Устройство успешно прикреплено"
#: views.py:535
#: views.py:545
msgid "Device your selected already does not exist"
msgstr "Устройство, выбранное вами, уже не существует"
#: views.py:554
#: views.py:564
msgid "Device has successfully unattached"
msgstr "Устройство успешно откреплено"
#: views.py:600
#: views.py:610
msgid "Group what you want doesn't exist"
msgstr "Указанная вами группа не найдена"
#: views.py:625
#: views.py:635
msgid "Extra field successfully created"
msgstr "Динамичесое поле добавлено успешно"
#: views.py:655
#: views.py:665
msgid "Extra fields has been saved"
msgstr "Динамические поля сохранены"
#: views.py:657
#: views.py:667
msgid "One or more extra fields has not been saved"
msgstr "Поле или одно из полей не найдено"
#: views.py:669
#: views.py:679
msgid "Extra field successfully deleted"
msgstr "Динамическое поле успешно удалено"
#: views.py:680
#: views.py:690
msgid "no ping"
msgstr "не пингуется"
#: views.py:683
#: views.py:693
msgid "Ip not passed"
msgstr "Ip адрес не передан"
#: views.py:689 views.py:702
#: views.py:699 views.py:712
msgid "ping ok"
msgstr "пингуется"
#: views.py:695
#: views.py:705
#, python-format
msgid "IP Conflict! %(all)d/%(return)d results"
msgstr "IP Конфликт! ping %(all)d из %(return)d"
#: views.py:697
#: views.py:707
#, python-format
msgid "ok ping, %(all)d/%(return)d loses"
msgstr "пингуется, %(all)d/%(return)d"
#: views.py:700
#: views.py:710
#, python-format
msgid "no ping, %(all)d/%(return)d loses"
msgstr "не пингуется, %(all)d/%(return)d"
#: views.py:743
#: views.py:753
msgid "Method is not POST"
msgstr "Метод не POST"
#: views.py:759
#: views.py:767
#, python-format
msgid ""
"<a href='%(user_url)s'>%(user_name)s</a> already pinned to this port on this "
"device"
msgstr ""
"<a href='%(user_url)s'>%(user_name)s</a> уже привязан к этому порту на этом "
"устройстве"
#: views.py:781
msgid "User port has been saved"
msgstr "Порт абонента успешно выбран"
#: views.py:761
#: views.py:783
msgid "Selected port does not exist"
msgstr "Выбранный порт не существует"
#: views.py:763
#, fuzzy
#| msgid "Abon does not exist"
#: views.py:785
msgid "User does not exist"
msgstr "Абонент не найден"
#: views.py:775
#: views.py:797
msgid "Street successfully saved"
msgstr "Улица успешно сохранена"
#: views.py:799
#: views.py:821
msgid "Streets has been saved"
msgstr "Улицы сохранены"
#: views.py:807
#: views.py:829
msgid "One of these streets has not been found"
msgstr "Одна из этих улиц не была найдена"
#: views.py:818
#: views.py:840
msgid "The street successfully deleted"
msgstr "Улица успешно удалена"
#: views.py:820
#: views.py:842
msgid "The street has not been found"
msgstr "Улица не найдена"
#: views.py:847
#: views.py:869
msgid "New telephone has been saved"
msgstr "Новый телефон сохранен"
#: views.py:867
#: views.py:889
msgid "Additional telephone successfully deleted"
msgstr "Номер телефона успешно удалён"
#: views.py:869
#: views.py:891
msgid "Telephone not found"
msgstr "Телефон не найден"
msgid "Enter a valid integer."
msgstr "Введите верное число"
#: views.py:967
msgid "Periodic pays has been designated"
msgstr "Периодический платёж назначен"
#: views.py:969
msgid "Something wrong in form"
msgstr "Что-то не так в форме"
#: views.py:988
msgid "Periodic pay successfully deleted"
msgstr "Периодический платёж успешно удалён"
#~ msgid "Graph of use"
#~ msgstr "График использования"
msgid "Enter a valid MAC Address."
msgstr "Введите валидный mac адрес"
#~ msgid "Enter a valid integer."
#~ msgstr "Введите верное число"
msgid "Port"
msgstr "Порт"
#~ msgid "Enter a valid MAC Address."
#~ msgstr "Введите валидный mac адрес"
msgid "Priority"
msgstr "Приоритет"
#~ msgid "Port"
#~ msgstr "Порт"
msgid "Delete service"
msgstr "Удалить услугу"
#~ msgid "Priority"
#~ msgstr "Приоритет"
msgid "Services of subscribers not found"
msgstr "Нет подключённых абоненту услуг"
#~ msgid "Services of subscribers not found"
#~ msgstr "Нет подключённых абоненту услуг"
msgid "Buy"
msgstr "Купить"
#~ msgid "Buy"
#~ msgstr "Купить"
msgid "Not confirmed"
msgstr "Действие не подтверждено"
#~ msgid "Not confirmed"
#~ msgstr "Действие не подтверждено"
msgid "Services"
msgstr "Услуги"
#~ msgid "Services"
#~ msgstr "Услуги"
msgid "Payments"
msgstr "Финансы"
@ -980,18 +1076,3 @@ msgstr "Инфо."
msgid "Dialing"
msgstr "Звонки"
msgid "No have ip"
msgstr "Нет ip адреса"
#, python-format
msgid "Graph of use by %(wantdate_d)s"
msgstr "График использования за %(wantdate_d)s"
msgid "Show graph by date"
msgstr "Показать график по дате"
#: views.py:757
#, python-format
msgid "<a href='%(user_url)s'>%(user_name)s</a> already pinned to this port on this device"
msgstr "<a href='%(user_url)s'>%(user_name)s</a> уже привязан к этому порту на этом устройстве"

130
abonapp/migrations/0004_auto_20180122_1732.py

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-22 17:32
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tariff_app', '0006_auto_20180122_1732'),
('abonapp', '0003_auto_20170927_1838'),
]
operations = [
migrations.CreateModel(
name='PeriodicPayForId',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_pay', models.DateTimeField(blank=True, null=True, verbose_name='Last pay time')),
('next_pay', models.DateTimeField(verbose_name='Next time to pay')),
],
options={
'db_table': 'periodic_pay_for_id',
},
),
migrations.AlterModelOptions(
name='abon',
options={'ordering': ['fio'], 'permissions': (('can_buy_tariff', 'Buy service perm'), ('can_view_passport', 'Can view passport'), ('can_add_ballance', 'fill account'), ('can_ping', 'Can ping')), 'verbose_name': 'Abon', 'verbose_name_plural': 'Abons'},
),
migrations.AlterModelOptions(
name='abongroup',
options={'ordering': ['title'], 'permissions': (('can_view_abongroup', 'Can view subscriber group'),), 'verbose_name': 'Abon group', 'verbose_name_plural': 'Abon groups'},
),
migrations.AlterModelOptions(
name='abonlog',
options={'ordering': ['-date'], 'permissions': (('can_view_abonlog', 'Can view subscriber logs'),)},
),
migrations.AlterModelOptions(
name='abonstreet',
options={'ordering': ['name'], 'verbose_name': 'Street', 'verbose_name_plural': 'Streets'},
),
migrations.AlterModelOptions(
name='abontariff',
options={'ordering': ['time_start'], 'permissions': (('can_complete_service', 'finish service perm'),), 'verbose_name': 'Abon service', 'verbose_name_plural': 'Abon services'},
),
migrations.AlterModelOptions(
name='additionaltelephone',
options={'ordering': ('owner_name',), 'permissions': (('can_view_additionaltelephones', 'Can view additional telephones'),), 'verbose_name': 'Additional telephone', 'verbose_name_plural': 'Additional telephones'},
),
migrations.AlterModelOptions(
name='allpaylog',
options={'ordering': ['-date_action']},
),
migrations.AlterModelOptions(
name='alltimepaylog',
options={'ordering': ['-date_add']},
),
migrations.AlterModelOptions(
name='invoiceforpayment',
options={'ordering': ('date_create',), 'permissions': (('can_view_invoiceforpayment', 'Can view invoice for payment'),), 'verbose_name': 'Debt', 'verbose_name_plural': 'Debts'},
),
migrations.AlterModelOptions(
name='passportinfo',
options={'verbose_name': 'Passport Info', 'verbose_name_plural': 'Passport Info'},
),
migrations.AddField(
model_name='alltimepaylog',
name='abon',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='abonapp.Abon'),
),
migrations.AddField(
model_name='alltimepaylog',
name='receipt_num',
field=models.IntegerField(default=0, verbose_name='Receipt number'),
),
migrations.AddField(
model_name='alltimepaylog',
name='trade_point',
field=models.CharField(blank=True, default=None, max_length=20, null=True, verbose_name='Trade point'),
),
migrations.AlterField(
model_name='abon',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='Comment'),
),
migrations.AlterField(
model_name='abon',
name='group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonGroup', verbose_name='User group'),
),
migrations.AlterField(
model_name='abon',
name='house',
field=models.CharField(blank=True, max_length=12, null=True, verbose_name='House'),
),
migrations.AlterField(
model_name='abon',
name='street',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='abonapp.AbonStreet', verbose_name='Street'),
),
migrations.AlterField(
model_name='abonlog',
name='author',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='additionaltelephone',
name='telephone',
field=models.CharField(max_length=16, validators=[django.core.validators.RegexValidator('^\\+[7,8,9,3]\\d{10,11}$')], verbose_name='Telephone'),
),
migrations.AlterField(
model_name='extrafieldsmodel',
name='field_type',
field=models.CharField(choices=[('int', 'Digital field'), ('str', 'Text field'), ('dbl', 'Floating field'), ('ipa', 'Ip Address')], default='str', max_length=3),
),
migrations.AddField(
model_name='periodicpayforid',
name='account',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abonapp.Abon', verbose_name='Account'),
),
migrations.AddField(
model_name='periodicpayforid',
name='periodic_pay',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tariff_app.PeriodicPay', verbose_name='Periodic pay'),
),
]

65
abonapp/models.py

@ -5,11 +5,11 @@ from django.core.validators import RegexValidator
from django.db.models.signals import post_save, post_delete, pre_delete, post_init
from django.dispatch import receiver
from django.utils import timezone
from django.db import models, connection
from django.db import models, connection, transaction
from django.core import validators
from django.utils.translation import ugettext as _
from agent import Transmitter, AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from tariff_app.models import Tariff
from tariff_app.models import Tariff, PeriodicPay
from accounts_app.models import UserProfile, MyUserManager
from mydefs import MyGenericIPAddressField, ip2int, LogicError, ip_addr_regex
from django.conf import settings
@ -30,6 +30,7 @@ class AbonGroup(models.Model):
)
verbose_name = _('Abon group')
verbose_name_plural = _('Abon groups')
ordering = ['title']
def __str__(self):
return self.title
@ -38,7 +39,7 @@ class AbonGroup(models.Model):
class AbonLog(models.Model):
abon = models.ForeignKey('Abon', models.CASCADE)
amount = models.FloatField(default=0.0)
author = models.ForeignKey(UserProfile, models.CASCADE, related_name='+')
author = models.ForeignKey(UserProfile, models.CASCADE, related_name='+', blank=True, null=True)
comment = models.CharField(max_length=128)
date = models.DateTimeField(auto_now_add=True)
@ -47,6 +48,7 @@ class AbonLog(models.Model):
permissions = (
('can_view_abonlog', _('Can view subscriber logs')),
)
ordering = ['-date']
def __str__(self):
return self.comment
@ -82,6 +84,7 @@ class AbonTariff(models.Model):
)
verbose_name = _('Abon service')
verbose_name_plural = _('Abon services')
ordering = ['time_start']
class AbonStreet(models.Model):
@ -95,6 +98,7 @@ class AbonStreet(models.Model):
db_table = 'abon_street'
verbose_name = _('Street')
verbose_name_plural = _('Streets')
ordering = ['name']
class ExtraFieldsModel(models.Model):
@ -173,14 +177,18 @@ class Abon(UserProfile):
('can_add_ballance', _('fill account')),
('can_ping', _('Can ping'))
)
unique_together = ('device', 'dev_port')
# TODO: Fix when duplicates already in database
#unique_together = ('device', 'dev_port')
verbose_name = _('Abon')
verbose_name_plural = _('Abons')
ordering = ['fio']
# Платим за что-то
# pay something
def make_pay(self, curuser, how_match_to_pay=0.0):
post_save.disconnect(abon_post_save, sender=Abon)
self.ballance -= how_match_to_pay
self.save(update_fields=['ballance'])
post_save.connect(abon_post_save, sender=Abon)
# Пополняем счёт
def add_ballance(self, current_user, amount, comment):
@ -318,8 +326,8 @@ class InvoiceForPayment(models.Model):
class AllTimePayLogManager(models.Manager):
def by_days(self):
@staticmethod
def by_days():
cur = connection.cursor()
cur.execute(r'SELECT SUM(summ) as alsum, DATE_FORMAT(date_add, "%Y-%m-%d") AS pay_date FROM all_time_pay_log '
r'GROUP BY DATE_FORMAT(date_add, "%Y-%m-%d")')
@ -333,9 +341,12 @@ class AllTimePayLogManager(models.Manager):
# Log for pay system "AllTime"
class AllTimePayLog(models.Model):
abon = models.ForeignKey(Abon, models.SET_DEFAULT, blank=True, null=True, default=None)
pay_id = models.CharField(max_length=36, unique=True, primary_key=True)
date_add = models.DateTimeField(auto_now_add=True)
summ = models.FloatField(default=0.0)
trade_point = models.CharField(_('Trade point'), max_length=20, default=None, null=True, blank=True)
receipt_num = models.IntegerField(_('Receipt number'), default=0)
objects = AllTimePayLogManager()
@ -344,7 +355,7 @@ class AllTimePayLog(models.Model):
class Meta:
db_table = 'all_time_pay_log'
ordering = ('date_add',)
ordering = ['-date_add']
# log for all terminals
@ -359,7 +370,7 @@ class AllPayLog(models.Model):
class Meta:
db_table = 'all_pay_log'
ordering = ('date_action',)
ordering = ['-date_action']
class AbonRawPassword(models.Model):
@ -396,6 +407,42 @@ class AdditionalTelephone(models.Model):
verbose_name_plural = _('Additional telephones')
class PeriodicPayForId(models.Model):
periodic_pay = models.ForeignKey(PeriodicPay, models.CASCADE, verbose_name=_('Periodic pay'))
last_pay = models.DateTimeField(_('Last pay time'), blank=True, null=True)
next_pay = models.DateTimeField(_('Next time to pay'))
account = models.ForeignKey(Abon, models.CASCADE, verbose_name=_('Account'))
def payment_for_service(self, author=None, comment=None):
"""
Charge for the service and leave a log about it
"""
now = timezone.now()
if self.next_pay < now:
pp = self.periodic_pay
amount = pp.calc_amount()
next_pay_date = pp.get_next_time_to_pay(self.last_pay)
abon = self.account
with transaction.atomic():
abon.make_pay(author, amount)
AbonLog.objects.create(
abon=abon, amount=-amount,
author=author,
comment=comment or _('Charge for "%(service)s"') % {
'service': self.periodic_pay
}
)
self.last_pay = now
self.next_pay = next_pay_date
self.save(update_fields=['last_pay', 'next_pay'])
def __str__(self):
return "%s %s" % (self.periodic_pay, self.next_pay)
class Meta:
db_table = 'periodic_pay_for_id'
@receiver(post_save, sender=Abon)
def abon_post_save(sender, **kwargs):
instance, created = kwargs["instance"], kwargs["created"]

11
abonapp/pay_systems.py

@ -10,10 +10,6 @@ SECRET = getattr(settings, 'PAY_SECRET')
SERV_ID = getattr(settings, 'PAY_SERV_ID')
#?ACT=1&PAY_ACCOUNT=960849&SERVICE_ID=y832r92y8f9e&PAY_ID=3561234&TRADE_POINT=377&SIGN=32e533a72389fe4e93746509f9d672f8
#?ACT=4&PAY_ACCOUNT=960849&PAY_AMOUNT=1.00&RECEIPT_NUM=29096&SERVICE_ID=y832r92y8f9e&PAY_ID=3561234&TRADE_POINT=496&SIGN=c42161214099dba01e6ab008552bbd3d
def allpay(request):
def bad_ret(err_id):
@ -60,6 +56,8 @@ def allpay(request):
" <time_stamp>%s</time_stamp>\n" % current_date +\
"</pay-response>"
elif act == 4:
trade_point = safe_int(request.GET.get('TRADE_POINT'))
receipt_num = safe_int(request.GET.get('RECEIPT_NUM'))
abon = Abon.objects.get(username=pay_account)
pays = AllTimePayLog.objects.filter(pay_id=pay_id)
if pays.count() > 0:
@ -71,7 +69,10 @@ def allpay(request):
AllTimePayLog.objects.create(
pay_id=pay_id,
summ=pay_amount
summ=pay_amount,
abon=abon,
trade_point=trade_point,
receipt_num=receipt_num
)
current_date = timezone.now().strftime("%d.%m.%Y %H:%M:%S")
return "<?xml version='1.0' encoding='UTF-8'?>" \

36
abonapp/templates/abonapp/modal_periodic_pay.html

@ -0,0 +1,36 @@
{% load i18n %}
{% load bootstrap3 %}
<form action="{% url 'abonapp:add_periodic_pay' gid uid %}" method="post">{% csrf_token %}
<div class="modal-header primary">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"><span class="glyphicon glyphicon-plus"></span>{% trans 'Add periodic pay' %}</h4>
</div>
{% include 'message_block.html' %}
<div class="modal-body">
{% bootstrap_icon 'calendar' as ic %}
{% bootstrap_field form.periodic_pay addon_before=ic %}
{% bootstrap_icon 'time' as ic %}
{% bootstrap_field form.next_pay addon_before=ic %}
<script type="text/javascript">
$(function () {
$('#{{ form.next_pay.id_for_label }}').datetimepicker({
format: 'YYYY-MM-DD'
});
});
</script>
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</button>
<button type="reset" class="btn btn-sm btn-default">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Reset' %}
</button>
</div>
</div>
</form>

20
abonapp/templates/abonapp/payHistory.html

@ -6,7 +6,6 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{% trans 'Sub' %}</th>
<th>{% trans 'Sum' %}</th>
<th>{% trans 'Date of payment' %}</th>
<th>{% trans 'Author of payment' %}</th>
@ -16,21 +15,26 @@
<tbody>
{% for ph in pay_history %}
<tr>
<td><a href="{% url 'abonapp:abon_home' abon_group.pk abon.pk %}">{{ ph.abon.username }}</a></td>
<td>{{ ph.amount }}</td>
<td>{{ ph.date|date:'d F Y, H:i:s' }}</td>
<td><a target="_blank" href="{% url 'acc_app:other_profile' ph.author.pk %}">{{ ph.author.username }}</a></td>
<td>{{ ph.comment }}</td>
<td>{{ ph.amount }}</td>
<td>{{ ph.date|date:'d F Y, H:i:s' }}</td>
<td>
{% if ph.author %}
<a target="_blank" href="{% url 'acc_app:other_profile' ph.author.pk %}">{{ ph.author.username }}</a>
{% else %}
{% trans 'System' %}
{% endif %}
</td>
<td>{{ ph.comment }}</td>
</tr>
{% empty %}
<tr>
<td colspan="5">{% trans 'Payment history is empty' %}</td>
<td colspan="4">{% trans 'Payment history is empty' %}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="5" class="btn-group btn-group-sm">
<td colspan="4" class="btn-group btn-group-sm">
{% if perms.abonapp.can_add_ballance %}
<a href="{% url 'abonapp:abon_amount' abon_group.pk abon.pk %}" class="btn btn-default btn-modal">
<span class="glyphicon glyphicon-credit-card"></span> {% trans 'Fill account' %}

35
abonapp/templates/abonapp/service.html

@ -55,7 +55,7 @@
{% endif %}
{% if abon_tariff %}
<a href="{% url 'abonapp:unsubscribe_service' abon_group.pk abon.pk abon_tariff.pk %}" class="btn btn-danger">
<a href="{% url 'abonapp:unsubscribe_service' abon_group.pk abon.pk abon_tariff.pk %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Finish service' %}
</a>
{% endif %}
@ -65,7 +65,7 @@
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Услуги для заказа</h3>
<h3 class="panel-title">{% trans 'Services for buy' %}</h3>
</div>
<div class="panel-body">
<table class="table table-condensed">
@ -108,12 +108,41 @@
{% endwith %}
</tbody>
</table>
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-default" title="{% trans 'User groups' %}">
<a href="{% url 'abonapp:ch_group_tariff' abon_group.pk %}" class="btn btn-sm btn-primary" title="{% trans 'User groups' %}">
<span class="glyphicon glyphicon-cog"></span> {% trans 'Attach services to group' %}
</a>
</div>
</div>
</div>
{% if perms.tariff_app.can_view_periodic_pay %}
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Periodic pay' %}</h3>
</div>
<div class="panel-body">
{% if periodic_pay %}
<dl class="dl-horizontal">
<dt>{% trans 'Pay logic' %}</dt>
<dd>{{ periodic_pay.periodic_pay }}</dd>
<dt>{% trans 'Last pay' %}</dt>
<dd>{{ periodic_pay.last_pay|default:'Not yet paid' }}</dd>
<dt>{% trans 'Next time to pay' %}</dt>
<dd>{{ periodic_pay.next_pay|date:'d E Y' }}</dd>
</dl>
<a href="{% url 'abonapp:del_periodic_pay' abon_group.pk abon.pk periodic_pay.pk %}" class="btn btn-sm btn-danger">
<span class="glyphicon glyphicon-remove-circle"></span> {% trans 'Remove periodic pay' %}
</a>
{% else %}
<a href="{% url 'abonapp:add_periodic_pay' abon_group.pk abon.pk %}" class="btn btn-primary btn-sm btn-modal">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add periodic pay' %}
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

6
abonapp/urls_abon.py

@ -38,5 +38,9 @@ urlpatterns = [
url(r'^(?P<uid>\d+)/tel$', views.tels, name='telephones'),
url(r'^(?P<uid>\d+)/tel/add$', views.tel_add, name='telephone_new'),
url(r'^(?P<uid>\d+)/tel/del$', views.tel_del, name='telephone_del')
url(r'^(?P<uid>\d+)/tel/del$', views.tel_del, name='telephone_del'),
url(r'^(?P<uid>\d+)/periodic_pay$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^(?P<uid>\d+)/periodic_pay(?P<periodic_pay_id>\d+)$', views.add_edit_periodic_pay, name='add_periodic_pay'),
url(r'^(?P<uid>\d+)/periodic_pay(?P<periodic_pay_id>\d+)/del$', views.del_periodic_pay, name='del_periodic_pay')
]

59
abonapp/views.py

@ -21,7 +21,7 @@ from devapp.models import Device, Port as DevPort
from datetime import datetime, date, timedelta
from taskapp.models import Task
from dialing_app.models import AsteriskCDR
from statistics.models import getModel, get_dates
from statistics.models import getModel
from guardian.shortcuts import get_objects_for_user, assign_perm
from guardian.decorators import permission_required_or_403 as permission_required
@ -263,11 +263,21 @@ def abon_services(request, gid, uid):
raise PermissionDenied
abon = get_object_or_404(models.Abon, pk=uid)
if abon.group != grp:
messages.warning(request, _("User group id is not matches with group in url"))
return redirect('abonapp:abon_services', abon.group.id, abon.id)
try:
periodic_pay = models.PeriodicPayForId.objects.get(account=abon)
except models.PeriodicPayForId.DoesNotExist:
periodic_pay = None
return render(request, 'abonapp/service.html', {
'abon': abon,
'abon_tariff': abon.current_tariff,
'abon_group': abon.group,
'services': grp.tariffs.all()
'services': grp.tariffs.all(),
'periodic_pay': periodic_pay
})
@ -918,7 +928,6 @@ def reset_ip(request, gid, uid):
}))
@login_required
@mydefs.only_admins
def fin_report(request):
@ -937,6 +946,50 @@ def fin_report(request):
})
@login_required
@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
def add_edit_periodic_pay(request, gid, uid, periodic_pay_id=0):
if periodic_pay_id == 0:
if not request.user.has_perm('abonapp.add_periodicpayforid'):
raise PermissionDenied
periodic_pay_instance = models.PeriodicPayForId()
else:
if not request.user.has_perm('abonapp.change_periodicpayforid'):
raise PermissionDenied
periodic_pay_instance = get_object_or_404(models.PeriodicPayForId, pk=periodic_pay_id)
if request.method == 'POST':
frm = forms.PeriodicPayForIdForm(request.POST, instance=periodic_pay_instance)
if frm.is_valid():
abon = get_object_or_404(models.Abon, pk=uid)
inst = frm.save(commit=False)
inst.account = abon
inst.save()
messages.success(request, _('Periodic pays has been designated'))
else:
messages.error(request, _('Something wrong in form'))
return redirect('abonapp:abon_services', gid, uid)
else:
frm = forms.PeriodicPayForIdForm(instance=periodic_pay_instance)
return render_to_text('abonapp/modal_periodic_pay.html', {
'form': frm,
'gid': gid,
'uid': uid
}, request=request)
@login_required
@permission_required('abonapp.can_view_abongroup', (models.AbonGroup, 'pk', 'gid'))
@permission_required('abonapp.delete_periodicpayforid')
def del_periodic_pay(request, gid, uid, periodic_pay_id):
periodic_pay_instance = get_object_or_404(models.PeriodicPayForId, pk=periodic_pay_id)
if periodic_pay_instance.account.id != uid:
uid = periodic_pay_instance.account.id
periodic_pay_instance.delete()
messages.success(request, _('Periodic pay successfully deleted'))
return redirect('abonapp:abon_services', gid, uid)
# API's
def abons(request):

8
cron.py

@ -1,12 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djing.settings")
django.setup()
from django.utils import timezone
from django.db.models import signals
from abonapp.models import Abon, AbonTariff, abontariff_pre_delete
from abonapp.models import Abon, AbonTariff, abontariff_pre_delete, PeriodicPayForId
from agent import Transmitter, NasNetworkError, NasFailedResult
from mydefs import LogicError
@ -19,6 +18,11 @@ def main():
tm.sync_nas(users)
signals.pre_delete.connect(abontariff_pre_delete, sender=AbonTariff)
# manage periodic pays
ppays = PeriodicPayForId.objects.filter(next_pay__lt=timezone.now()).prefetch_related('account', 'periodic_pay')
for pay in ppays:
pay.payment_for_service()
if __name__ == "__main__":
try:

52
djing/local_settings.py.template

@ -0,0 +1,52 @@
"""
Custom settings for each system
"""
DEBUG = True
ALLOWED_HOSTS = ['*']
DEFAULT_FROM_EMAIL = 'admin@yoursite.com'
PAGINATION_ITEMS_PER_PAGE = 10
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!!!!!!!!!!!!!!!!!!!!!!!!YOUR SECRET KEY!!!!!!!!!!!!!!!!!!!!!!!!'
DATABASES = {
'default': {
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'isolation_level': 'read uncommitted'
},
#'ENGINE': 'django.db.backends.sqlite3',
#'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djing_db',
'USER': 'DJING_MYSQL_USERNAME', # You can change the user name
'PASSWORD': 'DJING_MYSQL_PASSWORD', # You can change the password
'HOST': 'localhost'
}
}
# service id for AllPay payment system
PAY_SERV_ID = '<service id>'
PAY_SECRET = '<secret>'
# path to asterisk dial records
DIALING_MEDIA = 'path/to/asterisk_records'
# DHCP lease time
DHCP_TIMEOUT = 14400
DEFAULT_SNMP_PASSWORD = 'public'
TELEGRAM_BOT_TOKEN = 'bot token'
TELEPHONE_REGEXP = r'^\+[7,8,9,3]\d{10,11}$'
ASTERISK_MANAGER_AUTH = {
'username': 'admin',
'password': 'password',
'host': '127.0.0.1'
}

59
djing/settings_example.py → djing/settings.py

@ -1,6 +1,13 @@
# -*- coding: utf-8 -*
import os
try:
from . import local_settings
except ImportError:
raise ImportError("You must create config file local_settings.py from template")
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
from django.urls import reverse_lazy
@ -9,12 +16,12 @@ from django.urls import reverse_lazy
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '!!!!!!!!!!!!!!!!!!!!!!!!YOUR SECRET KEY!!!!!!!!!!!!!!!!!!!!!!!!'
SECRET_KEY = local_settings.SECRET_KEY
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = local_settings.DEBUG or False
ALLOWED_HOSTS = ['*']
ALLOWED_HOSTS = local_settings.ALLOWED_HOSTS
# required for django-guardian
AUTHENTICATION_BACKENDS = (
@ -87,23 +94,13 @@ WSGI_APPLICATION = 'djing.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#'ENGINE': 'django.db.backends.mysql',
#'NAME': 'djingdb',
#'USER': 'USER', # You can change the user name
#'PASSWORD': 'PASSWORD', # You can change the password
#'HOST': 'localhost'
}
}
DATABASES = local_settings.DATABASES
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
@ -125,7 +122,7 @@ SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_COOKIE_HTTPONLY = True
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'ru-RU'
@ -137,12 +134,12 @@ USE_L10N = False
USE_TZ = False
DEFAULT_FROM_EMAIL = 'nerosketch@gmail.com'
DEFAULT_FROM_EMAIL = local_settings.DEFAULT_FROM_EMAIL
# Максимальный загружаемый файл 3.90625M (кратно размеру блока диска 4kb, 4000 блоков)
# Maximum file size is 3.90625M
FILE_UPLOAD_MAX_MEMORY_SIZE = 4096000
# Время жизни сессии, 1 сутки
# time to session live, 1 day
SESSION_COOKIE_AGE = 60 * 60 * 24
@ -154,7 +151,7 @@ if DEBUG:
STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),)
# Пример вывода: 16 сентября 2012
# Example output: 16 september 2018
DATE_FORMAT = 'd E Y'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
@ -167,23 +164,19 @@ LOGIN_URL = reverse_lazy('acc_app:login')
LOGIN_REDIRECT_URL = reverse_lazy('acc_app:profile')
LOGOUT_URL = reverse_lazy('acc_app:logout_link')
PAGINATION_ITEMS_PER_PAGE=10
PAGINATION_ITEMS_PER_PAGE = local_settings.PAGINATION_ITEMS_PER_PAGE
PAY_SERV_ID = '<service id>'
PAY_SECRET = '<secret>'
PAY_SERV_ID = local_settings.PAY_SERV_ID
PAY_SECRET = local_settings.PAY_SECRET
DIALING_MEDIA = 'path/to/asterisk_records'
DIALING_MEDIA = local_settings.DIALING_MEDIA
DHCP_TIMEOUT = 14400
DHCP_TIMEOUT = local_settings.DHCP_TIMEOUT
DEFAULT_SNMP_PASSWORD = 'public'
DEFAULT_SNMP_PASSWORD = local_settings.DEFAULT_SNMP_PASSWORD
TELEGRAM_BOT_TOKEN = 'bot token'
TELEGRAM_BOT_TOKEN = local_settings.TELEGRAM_BOT_TOKEN
TELEPHONE_REGEXP = r'^\+[7,8,9,3]\d{10,11}$'
ASTERISK_MANAGER_AUTH = {
'username': 'admin',
'password': 'password',
'host': '127.0.0.1'
}
ASTERISK_MANAGER_AUTH = local_settings.ASTERISK_MANAGER_AUTH

1
requirements.txt

@ -16,6 +16,7 @@ pid
django-guardian
pinax-theme-bootstrap
django-bootstrap3
django-jsonfield
requests
webdavclient
pyst2

44
tariff_app/base_intr.py

@ -1,29 +1,57 @@
# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod
# from abonapp import Abon
class TariffBase(metaclass=ABCMeta):
@abstractmethod
def calc_amount(self):
"""Считает итоговую сумму платежа"""
"""Calculates total amount of payment"""
raise NotImplementedError
@abstractmethod
def calc_deadline(self):
"""Считаем дату, до которой действкет тариф"""
"""Calculate deadline date"""
raise NotImplementedError
@staticmethod
def description():
"""Возвращает текстовое описание"""
"""
Usage in mydefs.MyChoicesAdapter for choices fields.
:return: human readable description
"""
raise NotImplementedError
@staticmethod
def manage_access(abon):
"""Управляет доступом абонента к услуге"""
"""Manage subscribers access to service"""
#assert isinstance(abon, Abon)
# если абонент не активен то выходим
if not abon.is_active: return False
# смотрим на текущую услугу
act_tar = abon.active_tariff()
# если есть услуга
if act_tar:
return True
class PeriodicPayCalcBase(metaclass=ABCMeta):
@abstractmethod
def calc_amount(self, model_object):
"""
:param model_object: it is a instance of models.PeriodicPay model
:return: float: amount for the service
"""
raise NotImplementedError
@abstractmethod
def get_next_time_to_pay(self, model_object, last_time_payment):
"""
:param model_object: it is a instance of models.PeriodicPay model
:param last_time_payment: May be None if first pay
:return: datetime.datetime: time for next pay
"""
raise NotImplementedError
@staticmethod
def description():
"""Return text description.
Uses in mydefs.MyChoicesAdapter for CHOICES fields"""
raise NotImplementedError

42
tariff_app/custom_tariffs.py

@ -2,16 +2,15 @@
from datetime import timedelta, datetime
from django.utils import timezone
from django.utils.translation import ugettext as _
from .base_intr import TariffBase
from .base_intr import TariffBase, PeriodicPayCalcBase
from calendar import monthrange
# from abonapp import AbonTariff
from random import uniform
class TariffDefault(TariffBase):
def __init__(self, abon_tariff):
#assert isinstance(abon_tariff, AbonTariff)
# assert isinstance(abon_tariff, AbonTariff)
self.abon_tariff = abon_tariff
# Базовый функционал считает стоимость пропорционально использованному времени
@ -62,11 +61,10 @@ class TariffDp(TariffDefault):
# Как в IS только не на время, а на 10 лет
class TariffCp(TariffDp):
def calc_deadline(self):
# делаем время окончания услуги на 10 лет вперёд
nw = timezone.now()
long_long_time = datetime(year=nw.year+10, month=nw.month, day=nw.day,
long_long_time = datetime(year=nw.year + 10, month=nw.month, day=nw.day,
hour=23, minute=59, second=59)
return long_long_time
@ -81,3 +79,35 @@ TARIFF_CHOICES = (
('Dp', TariffDp),
('Cp', TariffCp)
)
class PeriodicPayCalcDefault(PeriodicPayCalcBase):
def calc_amount(self, model_object):
return model_object.amount
def get_next_time_to_pay(self, model_object, last_time_payment):
# TODO: решить какой будет расёт периодических платежей
return datetime.now() + timedelta(days=30)
@staticmethod
def description():
return _('Default periodic pay')
class PeriodicPayCalcCustom(PeriodicPayCalcDefault):
def calc_amount(self, model_object):
"""
:param model_object: it is a instance of models.PeriodicPay model
:return: float: amount for the service
"""
return uniform(1, 10)
@staticmethod
def description():
return _('Custom periodic pay')
PERIODIC_PAY_CHOICES = (
('df', PeriodicPayCalcDefault),
('cs', PeriodicPayCalcCustom)
)

14
tariff_app/forms.py

@ -7,11 +7,9 @@ class TariffForm(forms.ModelForm):
class Meta:
model = models.Tariff
fields = '__all__'
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'descr': forms.TextInput(attrs={'class': 'form-control'}),
'speedIn': forms.NumberInput(attrs={'class': 'form-control'}),
'speedOut': forms.NumberInput(attrs={'class': 'form-control'}),
'amount': forms.NumberInput(attrs={'class': 'form-control'}),
'calc_type': forms.Select(attrs={'class': 'form-control'})
}
class PeriodicPayForm(forms.ModelForm):
class Meta:
model = models.PeriodicPay
exclude = ['when_add', 'extra_info']

69
tariff_app/migrations/0006_auto_20180122_1732.py

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-22 17:32
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('tariff_app', '0005_auto_20170502_2229'),
]
operations = [
migrations.CreateModel(
name='PeriodicPay',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, verbose_name='Periodic pay name')),
('when_add', models.DateTimeField(auto_now_add=True, verbose_name='When pay created')),
('calc_type', models.CharField(choices=[('df', 'Default periodic pay'), ('cs', 'Custom periodic pay')], default='df', max_length=2, verbose_name='Script type for calculations')),
('amount', models.FloatField(verbose_name='Total amount')),
('extra_info', jsonfield.fields.JSONField(default=dict)),
],
options={
'verbose_name': 'Periodic pay',
'verbose_name_plural': 'Periodic pays',
'db_table': 'periodic_pay',
'ordering': ['-id'],
'permissions': (('can_view_periodic_pay', 'Can view periodic pay'),),
},
),
migrations.AlterField(
model_name='tariff',
name='amount',
field=models.FloatField(default=0.0, verbose_name='Price'),
),
migrations.AlterField(
model_name='tariff',
name='calc_type',
field=models.CharField(choices=[('Df', 'Base calculate functionality'), ('Dp', 'IS'), ('Cp', 'Private service')], default='Df', max_length=2, verbose_name='Script'),
),
migrations.AlterField(
model_name='tariff',
name='descr',
field=models.CharField(max_length=256, verbose_name='Service description'),
),
migrations.AlterField(
model_name='tariff',
name='is_admin',
field=models.BooleanField(default=False, verbose_name='Tech service'),
),
migrations.AlterField(
model_name='tariff',
name='speedIn',
field=models.FloatField(default=0.0, verbose_name='Speed In'),
),
migrations.AlterField(
model_name='tariff',
name='speedOut',
field=models.FloatField(default=0.0, verbose_name='Speed Out'),
),
migrations.AlterField(
model_name='tariff',
name='title',
field=models.CharField(max_length=32, verbose_name='Service title'),
),
]

94
tariff_app/models.py

@ -1,27 +1,33 @@
# -*- coding: utf-8 -*-
from django.db import models
from .custom_tariffs import TariffBase, TARIFF_CHOICES
from datetime import datetime
from django.db import models, IntegrityError
from django.utils.translation import gettext_lazy as _
from django.dispatch import receiver
from .base_intr import TariffBase, PeriodicPayCalcBase
from .custom_tariffs import TARIFF_CHOICES, PERIODIC_PAY_CHOICES
from mydefs import MyChoicesAdapter
from jsonfield import JSONField
class Tariff(models.Model):
title = models.CharField(max_length=32)
descr = models.CharField(max_length=256)
speedIn = models.FloatField(default=0.0)
speedOut = models.FloatField(default=0.0)
amount = models.FloatField(default=0.0)
calc_type = models.CharField(max_length=2, default=TARIFF_CHOICES[0][0], choices=MyChoicesAdapter(TARIFF_CHOICES))
is_admin = models.BooleanField(default=False)
title = models.CharField(_('Service title'), max_length=32)
descr = models.CharField(_('Service description'), max_length=256)
speedIn = models.FloatField(_('Speed In'), default=0.0)
speedOut = models.FloatField(_('Speed Out'), default=0.0)
amount = models.FloatField(_('Price'), default=0.0)
calc_type = models.CharField(_('Script'), max_length=2, default=TARIFF_CHOICES[0][0],
choices=MyChoicesAdapter(TARIFF_CHOICES))
is_admin = models.BooleanField(_('Tech service'), default=False)
# Возвращает потомок класса TariffBase, методы которого дают нужную логику оплаты по тарифу
def get_calc_type(self):
ob = [TC for TC in TARIFF_CHOICES if TC[0] == self.calc_type]
if len(ob) > 0:
res_type = ob[0][1]
if not issubclass(res_type, TariffBase):
raise TypeError
return res_type
calc_code = self.calc_type
for choice_pair in TARIFF_CHOICES:
choice_code, logic_class = choice_pair
if choice_code == calc_code:
if not issubclass(logic_class, TariffBase):
raise TypeError
return logic_class
def calc_deadline(self):
calc_type = self.get_calc_type()
@ -30,3 +36,59 @@ class Tariff(models.Model):
def __str__(self):
return "%s (%.2f)" % (self.title, self.amount)
class PeriodicPay(models.Model):
name = models.CharField(_('Periodic pay name'), max_length=64)
when_add = models.DateTimeField(_('When pay created'), auto_now_add=True)
calc_type = models.CharField(_('Script type for calculations'), max_length=2, default='df',
choices=MyChoicesAdapter(PERIODIC_PAY_CHOICES))
amount = models.FloatField(_('Total amount'))
extra_info = JSONField()
def _get_calc_object(self):
"""
:return: subclass of custom_tariffs.PeriodicPayCalcBase with required
logic depending on the selected in database.
"""
calc_code = self.calc_type
for choice_pair in PERIODIC_PAY_CHOICES:
choice_code, logic_class = choice_pair
if choice_code == calc_code:
if not issubclass(logic_class, PeriodicPayCalcBase):
raise TypeError
return logic_class()
def get_next_time_to_pay(self, last_time_payment):
#
# last_time_payment may be None if it is a first payment
#
calc_obj = self._get_calc_object()
res = calc_obj.get_next_time_to_pay(self, last_time_payment)
if type(res) is not datetime:
raise TypeError
return res
def calc_amount(self):
calc_obj = self._get_calc_object()
res = calc_obj.calc_amount(self)
if type(res) is not float:
raise TypeError
return res
def __str__(self):
return self.name
class Meta:
db_table = 'periodic_pay'
permissions = (
('can_view_periodic_pay', _('Can view periodic pay')),
)
verbose_name = _('Periodic pay')
verbose_name_plural = _('Periodic pays')
ordering = ['-id']
@receiver(models.signals.pre_delete, sender=PeriodicPay)
def periodic_pay_pre_delete(sender, **kwargs):
raise IntegrityError('All linked abonapp.PeriodicPayForId will be removed, be careful')

67
tariff_app/templates/tariff_app/editTarif.html

@ -1,5 +1,6 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
@ -21,65 +22,27 @@
<h3 class="panel-title">{% if tarif_id == 0 %}{% trans 'Create' %}{% else %}{% trans 'Edit' %}{% endif %} {% trans 'tariff' %}</h3>
</div>
<div class="panel-body">
<form role="form" action="{% url 'tarifs:edit' tarif_id %}" method="post">{% csrf_token %}
<form action="{% url 'tarifs:edit' tarif_id %}" method="post">{% csrf_token %}
<div class="form-group">
<label for="id_title">{% trans 'Service title' %}</label>
{% bootstrap_icon 'tag' as ic %}
{% bootstrap_field form.title addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-tag"></span></span>
{{ form.title }}{{ form.title.errors }}
</div>
</div>
<div class="form-group">
<label for="id_descr">{% trans 'Service description' %}</label>
{% bootstrap_icon 'comment' as ic %}
{% bootstrap_field form.descr addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-comment"></span></span>
{{ form.descr }}{{ form.descr.errors }}
</div>
</div>
{% bootstrap_icon 'chevron-down' as ic %}
{% bootstrap_field form.speedIn addon_before=ic %}
<div class="form-group">
<label for="id_speedIn">{% trans 'Speed In' %}</label>
{% bootstrap_icon 'chevron-up' as ic %}
{% bootstrap_field form.speedOut addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-chevron-down"></span></span>
{{ form.speedIn }}{{ form.speedIn.errors }}
</div>
</div>
<div class="form-group">
<label for="id_speedOut">{% trans 'Speed Out' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-chevron-up"></span></span>
{{ form.speedOut }}{{ form.speedOut.errors }}
</div>
</div>
{% bootstrap_icon 'usd' as ic %}
{% bootstrap_field form.amount addon_before=ic %}
<div class="form-group">
<label for="id_amount">{% trans 'Price' %}</label>
{% bootstrap_icon 'link' as ic %}
{% bootstrap_field form.calc_type addon_before=ic %}
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-usd"></span></span>
{{ form.amount }}{{ form.amount.errors }}
</div>
</div>
<div class="form-group">
<label for="id_calc_type">{% trans 'Script' %}</label>
<div class="input-group">
<span class="input-group-addon"><span class="glyphicon glyphicon-link"></span></span>
{{ form.calc_type }}{{ form.calc_type.errors }}
</div>
</div>
<div class="checkbox">
<label>{{ form.is_admin }} {% trans 'Tech service' %}</label>
</div>
{% bootstrap_field form.is_admin %}
<div class="btn-group">
<button type="submit" class="btn btn-sm btn-primary">

36
tariff_app/templates/tariff_app/ext.html

@ -0,0 +1,36 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">{% trans 'Services' %}</li>
</ol>
{% include 'message_block.html' %}
<ul class="nav nav-tabs">
{% url 'tarifs:home' as tarhome %}
<li{% if tarhome == request.path %} class="active"{% endif %}>
<a href="{{ tarhome }}">
{% trans 'Services' %}
</a>
</li>
{% url 'tarifs:periodic_pays' as perlist %}
<li{% if perlist == request.path %} class="active"{% endif %}>
<a href="{{ perlist }}">
{% trans 'Periodic pays' %}
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
{% block content %}{% endblock %}
</div>
</div>
{% endblock %}

51
tariff_app/templates/tariff_app/periodic_pays/add_edit.html

@ -0,0 +1,51 @@
{% extends request.is_ajax|yesno:'bajax.html,base.html' %}
{% load i18n %}
{% load bootstrap3 %}
{% block main %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li><a href="{% url 'tarifs:periodic_pays' %}">{% trans 'Services' %}</a></li>
{% if pay_instance %}
<li class="active">{% trans 'Change periodic pay' %}</li>
{% else %}
<li class="active">{% trans 'Add new periodic pay' %}</li>
{% endif %}
</ol>
{% include 'message_block.html' %}
{% if pay_instance %}
<div class="page-header">
<h2>{{ pay_instance.name }}</h2>
</div>
{% url 'tarifs:periodic_pay_edit' pay_instance.id as form_url %}
{% else %}
{% url 'tarifs:periodic_pay_add' as form_url %}
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% trans 'Pay details' %}</h3>
</div>
<div class="panel-body">
<form action="{{ form_url }}" method="post" autocomplete="off">{% csrf_token %}
{% bootstrap_icon 'header' as ic %}
{% bootstrap_field form.name addon_before=ic %}
{% bootstrap_icon 'transfer' as ic %}
{% bootstrap_field form.calc_type addon_before=ic %}
{% bootstrap_icon 'rub' as ic %}
{% bootstrap_field form.amount addon_before=ic %}
{% trans 'Save' as ic %}
{% bootstrap_button ic button_class="btn-primary" icon="save" %}
</form>
</div>
</div>
{% endblock %}

62
tariff_app/templates/tariff_app/periodic_pays/list.html

@ -0,0 +1,62 @@
{% extends request.is_ajax|yesno:'nullcont.htm,tariff_app/ext.html' %}
{% load i18n %}
{% block content %}
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="col-sm-4">{% trans 'Periodic pay name' %}</th>
<th class="col-sm-2">{% trans 'When pay created' %}</th>
<th class="col-sm-3">{% trans 'Script type for calculations' %}</th>
<th class="col-sm-2">{% trans 'Total amount' %}</th>
<th class="col-sm-1">#</th>
</tr>
</thead>
<tbody>
{% trans 'Edit' as edtext %}
{% with can_view=perms.tariff_app.can_view_periodic_pay %}
{% for pay in pays %}
<tr>
<td>{{ pay.name }}</td>
<td>{{ pay.when_add|date:'j M Y' }}</td>
<td>{{ pay.get_calc_type_display }}</td>
<td>{{ pay.amount }}</td>
<td>
{% if can_view %}
<a href="{% url 'tarifs:periodic_pay_edit' pay.id %}" class="btn btn-sm btn-default" title="{{ edtext }}">
<span class="glyphicon glyphicon-edit"></span>
</a>
{% else %}
<a href="#" class="btn btn-sm btn-default" disabled>
<span class="glyphicon glyphicon-edit"></span>
</a>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="5">{% trans 'The list is empty' %}</td>
</tr>
{% endfor %}
{% endwith %}
</tbody>
{% if perms.tariff_app.add_periodicpay %}
<tfoot>
<tr>
<td colspan="5">
<a href="{% url 'tarifs:periodic_pay_add' %}" class="btn btn-sm btn-success">
<span class="glyphicon glyphicon-plus"></span> {% trans 'Add' %}
</a>
</td>
</tr>
</tfoot>
{% endif %}
</table>
</div>
{% include 'toolbar_page.html' with pag=pays %}
{% endblock %}

12
tariff_app/templates/tariff_app/tarifs.html

@ -1,15 +1,7 @@
{% extends 'base.html' %}
{% extends request.is_ajax|yesno:'nullcont.htm,tariff_app/ext.html' %}
{% load i18n %}
{% block main %}
{% block content %}
<ol class="breadcrumb">
<li><span class="glyphicon glyphicon-home"></span></li>
<li class="active">{% trans 'Tarifs' %}</li>
</ol>
{% include 'message_block.html' %}
<h3>{% trans 'Service list' %}</h3>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>

6
tariff_app/urls.py

@ -8,5 +8,9 @@ urlpatterns = [
url(r'^$', views.tarifs, name='home'),
url(r'^(?P<tarif_id>\d+)$', views.edit_tarif, name='edit'),
url(r'^add$', views.edit_tarif, name='add'),
url(r'^del(?P<tid>\d+)$', views.del_tarif, name='del')
url(r'^del(?P<tid>\d+)$', views.del_tarif, name='del'),
url(r'^periodic_pays$', views.periodic_pays, name='periodic_pays'),
url(r'^periodic_pays/add$', views.periodic_pay, name='periodic_pay_add'),
url(r'^periodic_pays/(?P<pay_id>\d+)$', views.periodic_pay, name='periodic_pay_edit')
]

45
tariff_app/views.py

@ -7,7 +7,7 @@ from django.contrib import messages
from django.core.exceptions import PermissionDenied
from guardian.decorators import permission_required_or_403 as permission_required
from .models import Tariff
from .models import Tariff, PeriodicPay
import mydefs
from . import forms
@ -15,7 +15,7 @@ from . import forms
@login_required
@mydefs.only_admins
def tarifs(request):
tars = Tariff.objects.all()
tars = Tariff.objects.order_by('title')
# фильтр
direct, field = mydefs.order_helper(request)
@ -72,3 +72,44 @@ def del_tarif(request, tid):
messages.error(request, _('Not have a confirmations of delete'))
return mydefs.res_success(request, 'tarifs:home')
return render_to_text('tariff_app/modal_del_warning.html', {'tid': tid}, request=request)
@login_required
@permission_required('tariff_app.can_view_periodic_pay')
def periodic_pays(request):
pays = PeriodicPay.objects.all()
pays = mydefs.pag_mn(request, pays)
return render(request, 'tariff_app/periodic_pays/list.html', {
'pays': pays
})
@login_required
def periodic_pay(request, pay_id=0):
if pay_id != 0:
pay_inst = get_object_or_404(PeriodicPay, pk=pay_id)
if not request.user.has_perm('tariff_app.change_periodicpay'):
raise PermissionDenied
else:
pay_inst = None
if not request.user.has_perm('tariff_app.add_periodicpay'):
raise PermissionDenied
if request.method == 'POST':
frm = forms.PeriodicPayForm(request.POST, instance=pay_inst)
if frm.is_valid():
new_periodic_pay = frm.save()
if pay_inst is None:
comment = _('New periodic pay successfully created')
else:
comment = _('Periodic pay has been changed')
messages.success(request, comment)
return redirect('tarifs:periodic_pay_edit', new_periodic_pay.pk)
else:
messages.error(request, _('Some fields were filled incorrect, please try again'))
else:
frm = forms.PeriodicPayForm(instance=pay_inst)
return render(request, 'tariff_app/periodic_pays/add_edit.html', {
'pay_instance': pay_inst,
'form': frm
})

6
taskapp/templates/taskapp/tasklist.html

@ -33,10 +33,10 @@
{% endif %}
{% endif %}
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank" title="{{ task.abon.description|default:'' }}" data-toggle="tooltip">{{ task.abon.get_full_name }}</a></td>
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" title="{{ task.abon.description|default:'' }}" data-toggle="tooltip">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:'' }} {{ task.abon.house|default:'' }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>
@ -51,7 +51,7 @@
{% else %}
{% trans 'Author does not exist' %}
{% endif %}
</td>
</td>
<td class="hidden-xs">{{ task.time_of_create|date:'d E H:i' }}</td>
<td class="btn-group btn-group-xs btn-group-justified">

4
taskapp/templates/taskapp/tasklist_all.html

@ -46,10 +46,10 @@
{% endif %}
{% endif %}
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>

4
taskapp/templates/taskapp/tasklist_failed.html

@ -33,10 +33,10 @@
{% endif %}
{% endif %}
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>

4
taskapp/templates/taskapp/tasklist_finish.html

@ -33,10 +33,10 @@
{% endif %}
{% endif %}
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}" target="_blank">{{ task.pk }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.pk %}">{{ task.pk }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>

4
taskapp/templates/taskapp/tasklist_own.html

@ -33,10 +33,10 @@
{% endif %}
{% endif %}
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.id %}" target="_blank">{{ task.id }}</a></td>
<td class="hidden-xs"><a href="{% url 'taskapp:view' task.id %}">{{ task.id }}</a></td>
{% if task.abon and task.abon.group %}
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}" target="_blank">{{ task.abon.get_full_name }}</a></td>
<td><a href="{% url 'abonapp:abon_home' task.abon.group.pk task.abon.pk %}">{{ task.abon.get_full_name }}</a></td>
<td>{{ task.abon.group.title }}, {{ task.abon.street|default:_('Not assigned') }} {{ task.abon.house|default:_('Not assigned') }}</td>
{% else %}
<td>{% trans 'User does not exist' %}</td>

Loading…
Cancel
Save