From 62c3aa8118f89264f4aa1cd87ea05276209a94a2 Mon Sep 17 00:00:00 2001 From: bashmak Date: Mon, 22 Jan 2018 19:17:12 +0300 Subject: [PATCH] 1) Maked periodic pays. 2) Settings reformat. 3) more logs --- .gitignore | 2 +- README.md | 3 + abonapp/forms.py | 6 + abonapp/locale/ru/LC_MESSAGES/django.po | 483 ++++++++++-------- abonapp/migrations/0004_auto_20180122_1732.py | 130 +++++ abonapp/models.py | 65 ++- abonapp/pay_systems.py | 11 +- .../templates/abonapp/modal_periodic_pay.html | 36 ++ abonapp/templates/abonapp/payHistory.html | 20 +- abonapp/templates/abonapp/service.html | 35 +- abonapp/urls_abon.py | 6 +- abonapp/views.py | 59 ++- cron.py | 8 +- djing/local_settings.py.template | 52 ++ djing/{settings_example.py => settings.py} | 59 +-- requirements.txt | 1 + tariff_app/base_intr.py | 44 +- tariff_app/custom_tariffs.py | 42 +- tariff_app/forms.py | 14 +- .../migrations/0006_auto_20180122_1732.py | 69 +++ tariff_app/models.py | 94 +++- .../templates/tariff_app/editTarif.html | 67 +-- tariff_app/templates/tariff_app/ext.html | 36 ++ .../tariff_app/periodic_pays/add_edit.html | 51 ++ .../tariff_app/periodic_pays/list.html | 62 +++ tariff_app/templates/tariff_app/tarifs.html | 12 +- tariff_app/urls.py | 6 +- tariff_app/views.py | 45 +- taskapp/templates/taskapp/tasklist.html | 6 +- taskapp/templates/taskapp/tasklist_all.html | 4 +- .../templates/taskapp/tasklist_failed.html | 4 +- .../templates/taskapp/tasklist_finish.html | 4 +- taskapp/templates/taskapp/tasklist_own.html | 4 +- 33 files changed, 1160 insertions(+), 380 deletions(-) create mode 100644 abonapp/migrations/0004_auto_20180122_1732.py create mode 100644 abonapp/templates/abonapp/modal_periodic_pay.html create mode 100644 djing/local_settings.py.template rename djing/{settings_example.py => settings.py} (74%) create mode 100644 tariff_app/migrations/0006_auto_20180122_1732.py create mode 100644 tariff_app/templates/tariff_app/ext.html create mode 100644 tariff_app/templates/tariff_app/periodic_pays/add_edit.html create mode 100644 tariff_app/templates/tariff_app/periodic_pays/list.html diff --git a/.gitignore b/.gitignore index 782eb81..04bc9fd 100644 --- a/.gitignore +++ b/.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 diff --git a/README.md b/README.md index ce73bb9..f49de3c 100644 --- a/README.md +++ b/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) diff --git a/abonapp/forms.py b/abonapp/forms.py index 36fa6b5..4f7da47 100644 --- a/abonapp/forms.py +++ b/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'] diff --git a/abonapp/locale/ru/LC_MESSAGES/django.po b/abonapp/locale/ru/LC_MESSAGES/django.po index 27e0b00..7ed374f 100644 --- a/abonapp/locale/ru/LC_MESSAGES/django.po +++ b/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 "<Не назначен>" -#: 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 "Порт устройства" -#: 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 "" +"%(user_name)s already pinned to this port on this " +"device" +msgstr "" +"%(user_name)s уже привязан к этому порту на этом " +"устройстве" + +#: 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 "%(user_name)s already pinned to this port on this device" -msgstr "%(user_name)s уже привязан к этому порту на этом устройстве" diff --git a/abonapp/migrations/0004_auto_20180122_1732.py b/abonapp/migrations/0004_auto_20180122_1732.py new file mode 100644 index 0000000..c2f90a7 --- /dev/null +++ b/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'), + ), + ] diff --git a/abonapp/models.py b/abonapp/models.py index e7d0e73..20a4509 100644 --- a/abonapp/models.py +++ b/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"] diff --git a/abonapp/pay_systems.py b/abonapp/pay_systems.py index cd7bccb..261be3c 100644 --- a/abonapp/pay_systems.py +++ b/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): " %s\n" % current_date +\ "" 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 "" \ diff --git a/abonapp/templates/abonapp/modal_periodic_pay.html b/abonapp/templates/abonapp/modal_periodic_pay.html new file mode 100644 index 0000000..ccbaf82 --- /dev/null +++ b/abonapp/templates/abonapp/modal_periodic_pay.html @@ -0,0 +1,36 @@ +{% load i18n %} +{% load bootstrap3 %} +
{% csrf_token %} + + + {% include 'message_block.html' %} + + + +
diff --git a/abonapp/templates/abonapp/payHistory.html b/abonapp/templates/abonapp/payHistory.html index fc55b95..f836fa4 100644 --- a/abonapp/templates/abonapp/payHistory.html +++ b/abonapp/templates/abonapp/payHistory.html @@ -6,7 +6,6 @@ - @@ -16,21 +15,26 @@ {% for ph in pay_history %} - - - - - + + + + {% empty %} - + {% endfor %} -
{% trans 'Sub' %} {% trans 'Sum' %} {% trans 'Date of payment' %} {% trans 'Author of payment' %}
{{ ph.abon.username }}{{ ph.amount }}{{ ph.date|date:'d F Y, H:i:s' }}{{ ph.author.username }}{{ ph.comment }}{{ ph.amount }}{{ ph.date|date:'d F Y, H:i:s' }} + {% if ph.author %} + {{ ph.author.username }} + {% else %} + {% trans 'System' %} + {% endif %} + {{ ph.comment }}
{% trans 'Payment history is empty' %}{% trans 'Payment history is empty' %}
+ {% if perms.abonapp.can_add_ballance %} {% trans 'Fill account' %} diff --git a/abonapp/templates/abonapp/service.html b/abonapp/templates/abonapp/service.html index 976b982..f48f47f 100644 --- a/abonapp/templates/abonapp/service.html +++ b/abonapp/templates/abonapp/service.html @@ -55,7 +55,7 @@ {% endif %} {% if abon_tariff %} - + {% trans 'Finish service' %} {% endif %} @@ -65,7 +65,7 @@
-

Услуги для заказа

+

{% trans 'Services for buy' %}

@@ -108,12 +108,41 @@ {% endwith %}
- + {% trans 'Attach services to group' %}
+ + {% if perms.tariff_app.can_view_periodic_pay %} +
+
+
+

{% trans 'Periodic pay' %}

+
+
+ {% if periodic_pay %} +
+
{% trans 'Pay logic' %}
+
{{ periodic_pay.periodic_pay }}
+
{% trans 'Last pay' %}
+
{{ periodic_pay.last_pay|default:'Not yet paid' }}
+
{% trans 'Next time to pay' %}
+
{{ periodic_pay.next_pay|date:'d E Y' }}
+
+ + {% trans 'Remove periodic pay' %} + + {% else %} + + {% trans 'Add periodic pay' %} + + {% endif %} +
+
+
+ {% endif %} {% endblock %} diff --git a/abonapp/urls_abon.py b/abonapp/urls_abon.py index 1bbf288..d8efa20 100644 --- a/abonapp/urls_abon.py +++ b/abonapp/urls_abon.py @@ -38,5 +38,9 @@ urlpatterns = [ url(r'^(?P\d+)/tel$', views.tels, name='telephones'), url(r'^(?P\d+)/tel/add$', views.tel_add, name='telephone_new'), - url(r'^(?P\d+)/tel/del$', views.tel_del, name='telephone_del') + url(r'^(?P\d+)/tel/del$', views.tel_del, name='telephone_del'), + + url(r'^(?P\d+)/periodic_pay$', views.add_edit_periodic_pay, name='add_periodic_pay'), + url(r'^(?P\d+)/periodic_pay(?P\d+)$', views.add_edit_periodic_pay, name='add_periodic_pay'), + url(r'^(?P\d+)/periodic_pay(?P\d+)/del$', views.del_periodic_pay, name='del_periodic_pay') ] diff --git a/abonapp/views.py b/abonapp/views.py index 0e2c356..ad7f498 100644 --- a/abonapp/views.py +++ b/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): diff --git a/cron.py b/cron.py index d932005..70c8d8b 100755 --- a/cron.py +++ b/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: diff --git a/djing/local_settings.py.template b/djing/local_settings.py.template new file mode 100644 index 0000000..7f84de3 --- /dev/null +++ b/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 = '' +PAY_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' +} diff --git a/djing/settings_example.py b/djing/settings.py similarity index 74% rename from djing/settings_example.py rename to djing/settings.py index c64fffa..70bcd99 100644 --- a/djing/settings_example.py +++ b/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 = '' -PAY_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 diff --git a/requirements.txt b/requirements.txt index 9b59c85..02e47bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ pid django-guardian pinax-theme-bootstrap django-bootstrap3 +django-jsonfield requests webdavclient pyst2 diff --git a/tariff_app/base_intr.py b/tariff_app/base_intr.py index caa6723..f03356b 100644 --- a/tariff_app/base_intr.py +++ b/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 diff --git a/tariff_app/custom_tariffs.py b/tariff_app/custom_tariffs.py index 21666b7..c3f6ff6 100644 --- a/tariff_app/custom_tariffs.py +++ b/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) +) diff --git a/tariff_app/forms.py b/tariff_app/forms.py index 8f7e9e1..9a7ed61 100644 --- a/tariff_app/forms.py +++ b/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'] diff --git a/tariff_app/migrations/0006_auto_20180122_1732.py b/tariff_app/migrations/0006_auto_20180122_1732.py new file mode 100644 index 0000000..a9785de --- /dev/null +++ b/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'), + ), + ] diff --git a/tariff_app/models.py b/tariff_app/models.py index 464cf31..6173277 100644 --- a/tariff_app/models.py +++ b/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') diff --git a/tariff_app/templates/tariff_app/editTarif.html b/tariff_app/templates/tariff_app/editTarif.html index 30ebb81..2e81345 100644 --- a/tariff_app/templates/tariff_app/editTarif.html +++ b/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 %}