Создание отчётов — различия между версиями
(Новая страница: «В ExpertBilling 1.4 внедрена принципиально новая система работы с отчётами. Сейчас вы сами сможет...») |
Admin (обсуждение | вклад) (→Кассовый чек) |
||
(не показано 38 промежуточных версий этого же участника) | |||
Строка 4: | Строка 4: | ||
Свои отчёты вы можете сделать интерактивными. К примеру, перед его формированием, предложить пользователю указать необходимый период времени или выбрать другие параметры. | Свои отчёты вы можете сделать интерактивными. К примеру, перед его формированием, предложить пользователю указать необходимый период времени или выбрать другие параметры. | ||
− | [[Файл:reports.png]] | + | [[Файл:reports.png|right|300px|thumb]] |
+ | |||
+ | Для генерации отчётов необходимо создать шаблоны отчётов. | ||
+ | Сделать это можно в меню "Главное меню"->"Шаблоны документов" | ||
+ | |||
+ | В левой части окна расположены категории отчётов. Часть отчётов предзадана и мы не рекомендуем их удалять. | ||
+ | Чтобы создать новый отчёт необходимо выбрать первый пункт в дереве типов шаблонов "-- Новый шаблон --" и указать параметры шаблона справа. | ||
+ | Выберите тип шаблона, его имя и задайте тело шаблона. С помощью кнопки "Сохранить" на панели инструментов сохраните его. Кнопка "Предпросмотр" служит для предварительного просмотра созданного шаблона | ||
+ | |||
+ | === Краткая информация по синтаксису шаблонов === | ||
+ | Обозначение переменных: | ||
+ | <pre> | ||
+ | ${value1} | ||
+ | ${object.value} | ||
+ | </pre> | ||
+ | Вызов функций и методов | ||
+ | <pre> | ||
+ | ${int(a)} | ||
+ | ${object.method()} | ||
+ | </pre> | ||
+ | Вычисления | ||
+ | <pre> | ||
+ | ${a+100} | ||
+ | ${(1/2)+200**7} | ||
+ | </pre> | ||
+ | |||
+ | Выполнение кода на языке python в теле отчёта | ||
+ | <pre> | ||
+ | <% | ||
+ | import datetime | ||
+ | #получение текущего времени | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | </pre> | ||
+ | Цикл for в теле отчёта | ||
+ | <pre> | ||
+ | %for account in accounts: | ||
+ | ${account.id}, ${account.username} | ||
+ | %endfor | ||
+ | </pre> | ||
+ | |||
+ | Получение списка доступных переменных и методов в объекте | ||
+ | ${dir(object)} | ||
+ | |||
+ | Пример, демонстрирующий работу функции: | ||
+ | Код: | ||
+ | <pre> | ||
+ | <% | ||
+ | #получаем учётную запись пользователя с id=100 | ||
+ | account=connection.get_model(100, "billservice_account") | ||
+ | %> | ||
+ | ${dir(account)} | ||
+ | </pre> | ||
+ | Результат: | ||
+ | ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_asdict', '_fromTuple', '_toTuple', 'address', 'allow_expresscards', 'allow_ipn_with_block', 'allow_ipn_with_minus', 'allow_ipn_with_null', 'allow_vpn_block', 'allow_vpn_null', 'allow_webcab', 'assign_dhcp_block', 'assign_dhcp_null', 'assign_ipn_ip_from_dhcp', 'associate_pppoe_mac', 'associate_pptp_ipn_ip', 'balance_blocked', 'ballance', 'city', 'city_id', 'comment', 'contactperson', 'contactperson_phone', 'contract', 'created', 'credit', 'delete', 'disabled_by_limit', 'elevator_direction', 'email', 'entrance', 'entrance_code', 'fullname', 'get', 'hasattr', 'house', 'house_bulk', 'house_id', 'id', 'ipn_added', 'ipn_ip_address', 'ipn_ipinuse_id', 'ipn_mac_address', 'ipn_speed', 'ipn_status', 'isnull', 'last_balance_null', 'nas_id', 'passport', 'passport_date', 'passport_given', 'password', 'phone_h', 'phone_m', 'postcode', 'private_passport_number', 'region', 'room', 'row', 'save', 'status', 'street', 'street_id', 'suspended', 'systemuser_id', 'username', 'vlan', 'vpn_ip_address', 'vpn_ipinuse_id', 'vpn_speed'] | ||
+ | |||
+ | Видим все доступные переменные из учётной записи пользователя. | ||
+ | Чтобы вставить в тело отчёта телефонный номер абонента, нужно написать ${account.phone_m}, баланс - ${account.ballance} и т.д. | ||
+ | |||
+ | === Доступ к rpc серверу из отчёта === | ||
+ | В самом начале работы отчёта уже присутствует переменная connection, которая хранит в себе подключение к rpc серверу. | ||
+ | Доступ к rpc серверу понадобится вам в том случае, если вы захотите получить доступ к данным, хранящимся в базе данных. Помимо доступа к данным вам будут доступны другие функции rpc сервера. Такие как сброс активной сессии на севрере доступа, активация/деактивация пользователя на сервере доступа и многие другие. | ||
+ | Основные функции | ||
+ | * '''sql'''() - выполнение запроса. Функция возвращает результат вопроса в виде списка объектов записей | ||
+ | * '''get_model'''(id, table_name) - получить конкретную запись из указанной таблицы по её id | ||
+ | * '''get_models'''(table_name) - получить список записей из указанной таблицы | ||
+ | |||
+ | Пример: | ||
+ | <pre> | ||
+ | <% | ||
+ | accounts = connection.get_models("billservice_account") | ||
+ | %> | ||
+ | <table> | ||
+ | <tr> | ||
+ | <td>id</td><td>username</td> | ||
+ | </tr> | ||
+ | %for account in accounts: | ||
+ | <tr> | ||
+ | <td>${account.id}</td><td>${account.username}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | </table> | ||
+ | </pre> | ||
+ | В примере производится выборка всех аккаунтов и вывод их id и имён пользователей в виде таблицы. | ||
+ | |||
+ | === Пример отчёта === | ||
+ | Этот отчёт запрашивает период времени и потом показывает суммы, на которые был пополнен баланс у каждого пользователя. | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm='ostatok') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <% | ||
+ | accounts = connection.get_models("billservice_account") | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | |||
+ | <center>Пополнения баланса за период <strong>С ${child.start_date}</strong> по <strong>${child.end_date}</strong></center> | ||
+ | <table border=1 width=100%> | ||
+ | <tr> | ||
+ | <td>Имя пользователя</td><td>ФИО</td><td>Сумма</td> | ||
+ | </tr> | ||
+ | % for account in accounts: | ||
+ | <% | ||
+ | account_summ = connection.sql(r"SELECT sum(summ) FROM billservice_transaction WHERE account_id=%s and created between '%s' and '%s';" % (account.id, child.start_date, child.end_date))[0].sum or 0 | ||
+ | %> | ||
+ | <tr> | ||
+ | <td>${account.username}</td><td>${account.fullname}</td><td>${account_summ}</td> | ||
+ | </tr> | ||
+ | % endfor | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | |||
+ | [[Файл:period1.png|center|800px]] | ||
+ | |||
+ | === Отчёт по vpn сессиям абонентов === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm='ostatok') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <% | ||
+ | sessions = connection.sql("""SELECT account_id, count(*) as sess_acount, | ||
+ | (SELECT username FROM billservice_account as acc WHERE acc.id=account_id) as username, | ||
+ | sum(bytes_in) as bytes_in, sum(bytes_out) as bytes_out | ||
+ | FROM radius_activesession | ||
+ | WHERE date_start between '%s' and '%s' GROUP BY account_id""" % (child.start_date, child.end_date)) | ||
+ | |||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <center>Объём трафика по vpn сессиям<strong>С ${child.start_date}</strong> по <strong>${child.end_date}</strong></center> | ||
+ | <table border=1 width=100%> | ||
+ | <tr> | ||
+ | <td>Имя пользователя</td><td>Принято</td><td>Отправлено</td><td>Количество сессий абонента</td> | ||
+ | </tr> | ||
+ | % for session in sessions: | ||
+ | <tr> | ||
+ | <td>${session.username}</td><td>${int((session.bytes_in or 0)/(1024*1024))} МБ</td><td>${int((session.bytes_out or 0)/(1024*1024))} МБ</td><td>${session.sess_acount}</td> | ||
+ | </tr> | ||
+ | % endfor | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | Результат: | ||
+ | [[Файл:report_sessions.png|center|800px]] | ||
+ | |||
+ | == Встроенные документы== | ||
+ | В системе существуют следующие виды шаблонов: | ||
+ | * Договор на подключение физ.лица | ||
+ | * Договор на подключение юр.лица | ||
+ | * Накладная на карты | ||
+ | * Шаблон карты | ||
+ | * Кассовый чек | ||
+ | * Информационное письмо | ||
+ | * Акт выполненных работ | ||
+ | * Счёт-фактура | ||
+ | * Отчёты | ||
+ | |||
+ | === Договор на подключение физ.лица === | ||
+ | Система позволяет создать несколько шаблонов этого типа. | ||
+ | В тело шаблона передаётся список из идентификаторов аккаунтов,если выбрана печать договоров для списка пользователей, или список, состоящий из одного элемента - id аккаунта, если печать производится из карточки аккаунта. | ||
+ | |||
+ | Доступные пременные: | ||
+ | <pre> | ||
+ | accounts - тип список | ||
+ | connection - подключение к rpc серверу | ||
+ | </pre> | ||
+ | |||
+ | Пример шаблона: | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | |||
+ | <% | ||
+ | import os | ||
+ | provider = connection.get_operator()[0] | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | %for account_id in accounts: | ||
+ | <% | ||
+ | account = connection.get_model(account_id, "billservice_account") | ||
+ | %> | ||
+ | ID: ${account.id}<br> | ||
+ | Договор номер ${account.contract}<br> | ||
+ | |||
+ | %endfor | ||
+ | </body> | ||
+ | </html> | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | Для печати договора вам потребуется заполнить информацию о провайдере в меню Help->About Operator | ||
+ | Получить эту информацию в шаблоне можно с помощью такой конструкции | ||
+ | <pre> | ||
+ | provider = connection.get_operator()[0] | ||
+ | </pre> | ||
+ | Список доступных полей у переменной provider: | ||
+ | <pre> | ||
+ | id | ||
+ | organization - название | ||
+ | unp | ||
+ | okpo | ||
+ | contactperson | ||
+ | director | ||
+ | phone | ||
+ | postaddress | ||
+ | uraddress | ||
+ | email | ||
+ | fax | ||
+ | bank_id | ||
+ | </pre> | ||
+ | Получить информацию о банке можно с помощью конструкции | ||
+ | <pre> | ||
+ | bank = connection.get_model(provide.bank_id, "billservice_bankdata") | ||
+ | </pre> | ||
+ | Доступные переменные: | ||
+ | <pre> | ||
+ | id | ||
+ | bank | ||
+ | bankcode | ||
+ | rs | ||
+ | </pre> | ||
+ | === Договор на подключение юр.лица === | ||
+ | Печать договора на подключение юр. лица ничем не отличается от физ. лиц за тем исключением, что для получения доступа к расширенному набору полей юрлиц вам необходимо воспользоваться конструкцией: | ||
+ | <pre> | ||
+ | org = connection.get("SELECT * FROM billservice_organization WHERE account_id=%s" % account.id) | ||
+ | </pre> | ||
+ | Доступные переменные: | ||
+ | <pre> | ||
+ | id | ||
+ | account_id | ||
+ | name | ||
+ | uraddress | ||
+ | okpo | ||
+ | unp | ||
+ | bank_id | ||
+ | phone | ||
+ | fax | ||
+ | kpp | ||
+ | kor_s | ||
+ | </pre> | ||
+ | Получить банк вы можете способом, описанным выше в шаблоне для физ. лиц. | ||
+ | |||
+ | === Накладная на карты === | ||
+ | Этот тип документа описывает факт передачи партии карт доступа/экспресс-оплаты/HotSpot дилеру. | ||
+ | В шаблон передаётся список объектов карт в переменной cards, объект с информацией о провайдере в переменной operator, объект дилера в переменной dealer, текущая дата в переменной created, cardcount - количество продаваемых карт, sum_for_pay - сумма к оплате, pay - сколько оплачено, discount - процент скидки, discount_sum - сумма скидки, paydeffer - отсрочка оплаты в днях. | ||
+ | |||
+ | Вид шаблона по-умолчанию: | ||
+ | <pre> | ||
+ | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" | ||
+ | "http://www.w3.org/TR/html4/loose.dtd"> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||
+ | </head> | ||
+ | |||
+ | <body> | ||
+ | <div style="width:100%; "> | ||
+ | <div style="float:right "> | ||
+ | <span style="font-weight:bold; ">Дилер</span><br> | ||
+ | Организация: ${dealer.organization}<br> | ||
+ | Директор: ${dealer.director}<br> | ||
+ | Юр адрес: ${dealer.uraddress}<br> | ||
+ | р/с: ${dealer.rs}<br> | ||
+ | УНН: ${dealer.unp}<br> | ||
+ | ОКПО: ${dealer.okpo}<br> | ||
+ | Банк: ${dealer.bank}, код ${dealer.bankcode}<br> | ||
+ | </div> | ||
+ | |||
+ | <div style="float:left "> | ||
+ | <span style="font-weight:bold; ">Оператор</span><br> | ||
+ | Организация: ${operator.organization}<br> | ||
+ | Директор: ${operator.director}<br> | ||
+ | Юр адрес: ${operator.uraddress}<br> | ||
+ | р/с: ${operator.rs} <br> | ||
+ | УНН: ${operator.unp}<br> | ||
+ | ОКПО: ${operator.okpo}<br> | ||
+ | Банк: ${operator.bank}, Код ${operator.bankcode}<br> | ||
+ | </div> | ||
+ | </div> | ||
+ | |||
+ | <div style="font-weight:bold; float:left; width:100%; text-align:center; margin-bottom:20px; margin-top:20px; "> | ||
+ | Накладная от ${created} | ||
+ | </div> | ||
+ | |||
+ | <div style="clear:both "></div> | ||
+ | <table border="1" align="center" style="width:100%"> | ||
+ | <tr> | ||
+ | <td>ID карты</td> | ||
+ | <td>Серия</td> | ||
+ | <td>Номинал</td> | ||
+ | <td>Активировать С</td> | ||
+ | <td>Активировать По</td> | ||
+ | </tr> | ||
+ | |||
+ | % for card in cards: | ||
+ | <tr> | ||
+ | <td>${card.id}</td> | ||
+ | <td>${card.series}</td> | ||
+ | <td>${card.nominal}</td> | ||
+ | <td>${card.start_date}</td> | ||
+ | <td>${card.end_date}</td> | ||
+ | </tr> | ||
+ | % endfor | ||
+ | </table> | ||
+ | |||
+ | Итого ${cardcount} карт на сумму: ${sum_for_pay}<br> | ||
+ | Скидка: ${discount} на сумму ${discount_sum}<br> | ||
+ | Оплачено: ${pay}<br> | ||
+ | Оплатить до:${paydeffer} | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | |||
+ | Результат работы шаблона: | ||
+ | [[Файл:sale_card.png|center|640px]] | ||
+ | |||
+ | === Шаблон карты === | ||
+ | Шаблоны карт предоплаты используется при печати карт в момент передачи их дилеру. | ||
+ | Учитывая то, что в одной поставке могут оказаться карты разных типов, номиналов и шаблонов, шаблон карты должен описывать только её внешний вид. | ||
+ | Изначально в шаблон передаётся подключение к rpc серверу с переменной connection, переменная operator с объектом, содержащим информацию о провайдере, список карт в переменной cards. | ||
+ | |||
+ | Каждый объект типа card содержит следующие переменные: | ||
+ | <pre> | ||
+ | id | ||
+ | series | ||
+ | pin | ||
+ | sold | ||
+ | nominal | ||
+ | start_date | ||
+ | end_date | ||
+ | disabled | ||
+ | created | ||
+ | template_id | ||
+ | account_id | ||
+ | tarif_id | ||
+ | nas_id | ||
+ | login | ||
+ | ip | ||
+ | ipinuse_id | ||
+ | </pre> | ||
+ | Предустановлен следующий шаблон карты по-умолчанию: | ||
+ | <pre> | ||
+ | <div> | ||
+ | <div>Карта пополнения на ${card.nominal} рублей</div> | ||
+ | <div>Для пополнения счета зайдите на сайт http://192.168.12.2 и введите свой логин и пароль. Далее нажмите "Активация карточки". В появившемся окне введите: </div> | ||
+ | <div>Серия: ${card.series}</div> | ||
+ | <div>ID: ${card.id}</div> | ||
+ | <div>PIN: ${card.pin}</div> | ||
+ | <div>Активировать до ${card.end_date} </div> | ||
+ | </div> | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | При печати к началу документа подставляется стандартный блок | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | </pre> | ||
+ | |||
+ | и | ||
+ | <pre> | ||
+ | </body></html> | ||
+ | </pre> | ||
+ | В конце сгенерированного файла. Таким образом не нарушается структура html документа. | ||
+ | |||
+ | '''Важно!!!''' Если вы планируете использовать в шаблонах карт картинки - указывайте полный путь к картинке на файловой системе или пользуйтесь конструкцией | ||
+ | <pre> | ||
+ | <% import os %> - поместить 1 раз в начале файла. | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | </pre> | ||
+ | Где images/reports/header.png - путь к файлу с картинкой относительно папки с интерфейсом администратора. | ||
+ | |||
+ | |||
+ | === Кассовый чек === | ||
+ | Шаблон кассового чека используется при печати чеков из интерфейса кассира и интерфейса администратора. | ||
+ | '''Важно!!!''' Не создавайте больше 1 шаблона кассового чека. | ||
+ | В шаблон чека передаётся переменная connection, содержащая подключение к базе данных, переменная transaction с выполненным платежом, переменная account с пользователем, которому пополняли баланс. | ||
+ | |||
+ | По-умолчанию шаблон чека выглядит следующим образом: | ||
+ | <pre> | ||
+ | |||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
+ | <style> | ||
+ | td{ | ||
+ | FONT: 9px Times New Roman; | ||
+ | } | ||
+ | h1{ | ||
+ | FONT: 9px Arial; | ||
+ | } | ||
+ | </style> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | tr=connection.get("SELECT id FROM billservice_transaction WHERE created='%s'" % transaction.created) | ||
+ | tarif_name = connection.get(" SELECT get_tarif_name(%s, now()::timestamp without time zone)" % account.id).get_tarif_name | ||
+ | %> | ||
+ | <table align=center width="85%"> | ||
+ | <tr> | ||
+ | <td> | ||
+ | <h1 align=center> <b> Квитанция об оплате услуг № ${tr.id} </b> </h1> | ||
+ | <strong>Абонент:</strong> ${account.fullname} <br> | ||
+ | <strong>Тарифный план:</strong> ${tarif_name} <br> | ||
+ | <strong>Логин:</strong> ${account.username}<br> | ||
+ | <strong>Сумма:</strong> ${transaction.summ}<br> | ||
+ | <strong>Дата приема платежа:</strong> ${transaction.created}<br> | ||
+ | </td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | </body> | ||
+ | </html> | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | Доступные переменные в transaction: | ||
+ | <pre> | ||
+ | bill | ||
+ | account_id | ||
+ | type_id | ||
+ | approved | ||
+ | tarif_id | ||
+ | summ | ||
+ | description | ||
+ | created | ||
+ | systemuser_id | ||
+ | promise | ||
+ | end_promise | ||
+ | promise_expired | ||
+ | accounttarif_id | ||
+ | </pre> | ||
+ | Обратите внимание, что в базе данных пополнения хранятся с минусом, а списания с плюсом. Поэтому при выводе в чеке суммы, её нужно умножить на -1. ${-1*transaction.summ} | ||
+ | Если вы хотите дополнить шаблон чека информацией о кассире, выполнившем платёж - его можно получить следующим выражением: | ||
+ | <% | ||
+ | user=connection.get_model(transaction.systemuser_id, "billservice_systemuser") | ||
+ | %> | ||
+ | Доступные переменные системного пользователя: | ||
+ | <pre> | ||
+ | id | ||
+ | username | ||
+ | password | ||
+ | last_ip | ||
+ | last_login | ||
+ | description | ||
+ | created | ||
+ | status | ||
+ | host | ||
+ | role | ||
+ | text_password | ||
+ | email | ||
+ | job | ||
+ | fullname | ||
+ | address | ||
+ | home_phone | ||
+ | mobile_phone | ||
+ | passport | ||
+ | passport_details | ||
+ | passport_number | ||
+ | unp | ||
+ | im | ||
+ | </pre> | ||
+ | |||
+ | === Информационное письмо === | ||
+ | Информационное письмо используется при рассылке уведомлений о приближении баланса к нулю. Рассылку осуществляет скрипт sendmail.py, находящийся в папке с биллингом. Настройка скрипта производится в файле ebs_config.ini | ||
+ | |||
+ | Шаблон письма по-умолчанию: | ||
+ | <pre> | ||
+ | --------------------------------------------------- | ||
+ | Это сообщение сгенерировано биллинговой системой! | ||
+ | --------------------------------------------------- | ||
+ | |||
+ | Здравствуйте, ${account.username}. | ||
+ | Уведомляем, что актуальный баланс Вашего лицевого счета составляет ${"%.2f" % account.ballance} руб. Размер кредита ${account.credit}. | ||
+ | Пожалуйста, пополните баланс во избежание блокировки. | ||
+ | --- | ||
+ | ${operator.organization} | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | === Акт выполненных работ === | ||
+ | |||
+ | === Счёт-фактура === | ||
+ | |||
+ | == Отчёты == | ||
+ | ===Отчёт по текущему списку активированных подключаемых услуг=== | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | font-size:10pt; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | accountaddonservices=connection.sql("""select (select name from billservice_addonservice WHERE id=aas.service_id) as name, | ||
+ | (select username from billservice_account WHERE id=aas.account_id) as username, | ||
+ | (select username from billservice_subaccount WHERE id=aas.subaccount_id) as subacc_username, | ||
+ | activated, temporary_blocked, | ||
+ | (select cost FROM billservice_addonservice WHERE id=aas.service_id) as cost | ||
+ | from billservice_accountaddonservice as aas | ||
+ | WHERE deactivated is not Null | ||
+ | ORDER BY activated; | ||
+ | """) | ||
+ | cost=0 | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Активные подключаемые услуги:</strong></div> | ||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <td>Название услуги</td><td>Цена</td><td>Имя аккаунта</td><td>Имя субаккаунта</td><td>Дата активации</td><td>Блокировка</td> | ||
+ | </tr> | ||
+ | %for aas in accountaddonservices: | ||
+ | <% cost+=aas.cost %> | ||
+ | <tr> | ||
+ | <td>${aas.name}</td><td>${aas.cost}</td><td>${aas.username}</td><td>${aas.subacc_username if aas.subacc_username else ''}</td><td>${aas.activated}</td><td>${aas.temporary_blocked if aas.temporary_blocked else u'Нет'}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | <tr> | ||
+ | <td></td><td></td><td>Итого: ${cost}</td><td></td><td></td><td></td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | Пример работы: | ||
+ | [[Файл:activated_addonservices.png|center|640px]] | ||
+ | |||
+ | === Отчёт по списку активированных услуг за период=== | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm=u'Период') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | font-size:10pt; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | accountaddonservices=connection.sql("""select (select name from billservice_addonservice WHERE id=aas.service_id) as name, | ||
+ | (select username from billservice_account WHERE id=aas.account_id) as username, | ||
+ | (select username from billservice_subaccount WHERE id=aas.subaccount_id) as subacc_username, | ||
+ | activated, deactivated, | ||
+ | (select cost FROM billservice_addonservice WHERE id=aas.service_id) as cost | ||
+ | from billservice_accountaddonservice as aas | ||
+ | WHERE activated between '%s' and '%s' | ||
+ | ORDER BY activated; | ||
+ | """ % (child.start_date, child.end_date,)) | ||
+ | cost=0 | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Активированные подключаемые услуги:</strong></div> | ||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <td>Название услуги</td><td>Цена</td><td>Имя аккаунта</td><td>Имя субаккаунта</td><td>Дата активации</td><td>Дата деактивации</td> | ||
+ | </tr> | ||
+ | %for aas in accountaddonservices: | ||
+ | <% cost+=aas.cost %> | ||
+ | <tr> | ||
+ | <td>${aas.name}</td><td>${aas.cost}</td><td>${aas.username}</td><td>${aas.subacc_username if aas.subacc_username else ''}</td><td>${aas.activated}</td><td>${aas.deactivated if aas.deactivated else ''}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | <tr> | ||
+ | <td></td><td></td><td>Итого: ${cost}</td><td></td><td></td><td></td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | Пример работы: | ||
+ | [[Файл:accauntaddonservices_period.png|center|640px]] | ||
+ | |||
+ | === Отчёт по изменениям тарифного плана за период === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body font-size='10pt'> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm=u'Период') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | font-size: 10pt; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | accounttarifs=connection.sql("""SELECT acc.username, tarif.name, act.datetime, act.periodical_billed, | ||
+ | (SELECT name FROM billservice_tariff | ||
+ | WHERE id=(SELECT tarif_id FROM billservice_accounttarif WHERE account_id=acc.id and datetime<act.datetime ORDER BY datetime DESC LIMIT 1)) as prev_tarif | ||
+ | FROM billservice_accounttarif as act | ||
+ | JOIN billservice_account as acc on acc.id=act.account_id | ||
+ | JOIN billservice_tariff as tarif ON tarif.id=act.tarif_id | ||
+ | WHERE act.datetime between '%s' and '%s' | ||
+ | ORDER BY act.datetime | ||
+ | """ % (child.start_date, child.end_date,)) | ||
+ | strftimeFormat = "%d.%m.%Y %H:%M:%S" | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Изменения тарифного плана:</strong></div> | ||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <td>Имя аккаунта</td><td>Название тарифа</td><td>Дата начала</td><td>Период закрыт</td><td>Предыдущий тариф</td> | ||
+ | </tr> | ||
+ | %for act in accounttarifs: | ||
+ | <tr> | ||
+ | <td>${act.username}</td><td>${act.name}</td><td>${act.datetime.strftime(strftimeFormat)}</td><td>${act.periodical_billed}</td><td>${act.prev_tarif if act.prev_tarif else ''}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | </table> | ||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | Пример работы | ||
+ | [[Файл:accounttarifs.png|center|640px]] | ||
+ | |||
+ | === Отчёт по списку занятых IP адресов === | ||
+ | Простой отчёт. | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | pools=connection.sql("""SELECT id, name FROM billservice_ippool""") | ||
+ | count=0 | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Занятые IP адреса в пулах:</strong></div> | ||
+ | %for pool in pools: | ||
+ | <strong>${pool.name}</strong><br/> | ||
+ | <% | ||
+ | ips=connection.sql("""SELECT DISTINCT ip FROM billservice_ipinuse WHERE disabled is Null and ip!='0.0.0.0' and pool_id=%s ORDER BY ip ASC""" % pool.id) | ||
+ | connection.commit() | ||
+ | %> | ||
+ | %for ip in ips: | ||
+ | ${ip.ip} <br/> | ||
+ | <% | ||
+ | count+=1 | ||
+ | %> | ||
+ | %endfor | ||
+ | %endfor | ||
+ | |||
+ | <tr> | ||
+ | <td></td><td></td><td>Итого: ${count}</td><td></td><td></td><td></td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | Пример работы | ||
+ | [[Файл:freeips.png|center|640px]] | ||
+ | |||
+ | === Отчёт по прибыли в день === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body font-size='10pt'> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm=u'Период') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | transactions=connection.sql("""SELECT sum(summ*-1), date_part('day',created) as day, date_part('month',created) as month, date_part('year',created) as year | ||
+ | FROM billservice_transaction | ||
+ | WHERE created between '%s' and '%s' | ||
+ | GROUP BY date_part('year',created),date_part('month',created),date_part('day',created) ORDER BY year,month, day ASC; | ||
+ | """ % (child.start_date, child.end_date,)) | ||
+ | cost=0 | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Отчёт по прибыли с группировкой по дням:</strong></div> | ||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <td>Год</td><td>Месяц</td><td>День</td><td>Сумма</td> | ||
+ | </tr> | ||
+ | %for transaction in transactions: | ||
+ | <% cost+=transaction.sum %> | ||
+ | <tr> | ||
+ | <td>${int(transaction.year)}</td><td>${int(transaction.month)}</td><td>${int(transaction.day)}</td><td>${transaction.sum if transaction.sum else ''}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | <tr> | ||
+ | <td></td><td></td><td><strong>Итого</strong></td><td> ${cost}</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | |||
+ | Пример работы | ||
+ | [[Файл:money_days.png|center|640px]] | ||
+ | |||
+ | === Отчёт по прибыли с группировкой по месяцам === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body font-size='10pt'> | ||
+ | <% | ||
+ | import os | ||
+ | from CustomForms import PeriodForm | ||
+ | child = PeriodForm(realm=u'Период') | ||
+ | if child.exec_()!=1: | ||
+ | return | ||
+ | %> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | from dateutil.relativedelta import relativedelta | ||
+ | now = datetime.datetime.now() | ||
+ | %> | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <% | ||
+ | transactions=connection.sql("""SELECT sum(summ*-1), date_part('month',created) as month, date_part('year',created) as year | ||
+ | FROM billservice_transaction | ||
+ | WHERE created between '%s' and '%s' | ||
+ | GROUP BY date_part('year',created),date_part('month',created) ORDER BY year,month ASC; | ||
+ | """ % (child.start_date, child.end_date,)) | ||
+ | cost=0 | ||
+ | %> | ||
+ | <div> | ||
+ | <strong>Отчёт по прибыли с группировкой по дням:</strong></div> | ||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <td>Год</td><td>Месяц</td><td>Сумма</td> | ||
+ | </tr> | ||
+ | %for transaction in transactions: | ||
+ | <% cost+=transaction.sum %> | ||
+ | <tr> | ||
+ | <td>${int(transaction.year)}</td><td>${int(transaction.month)}</td><td>${transaction.sum if transaction.sum else ''}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | <tr> | ||
+ | <td></td><td><strong>Итого</strong></td><td> ${cost}</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | |||
+ | Пример работы | ||
+ | [[Файл:money_months.png|center|640px]] | ||
+ | |||
+ | === Занятые порты на коммутаторах === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | </head> | ||
+ | <body> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: gray; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 1px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | |||
+ | <% | ||
+ | import os | ||
+ | commutators=connection.get_models("nas_switch") | ||
+ | subaccounts_data=connection.sql("""SELECT acc.username, subacc.switch_id, subacc.switch_port FROM billservice_account as acc | ||
+ | JOIN billservice_subaccount as subacc ON subacc.account_id=acc.id | ||
+ | WHERE acc.id in (SELECT account_id FROM billservice_subaccount WHERE switch_id is not Null and switch_port is not Null)""") | ||
+ | subaccounts={} | ||
+ | %> | ||
+ | %for subacc in subaccounts_data: | ||
+ | <% subaccounts[(subacc.switch_id,subacc.switch_port)]=subacc.username %> | ||
+ | %endfor | ||
+ | <img src="${os.path.abspath("images/reports/header.png")}"> | ||
+ | <div> | ||
+ | <strong>Отчёт по занятым портам коммутаторов:</strong></div> | ||
+ | |||
+ | <table class="sample" width="100%"> | ||
+ | <tr> | ||
+ | <th>Порт</th><th>Аккаунт</th> | ||
+ | </tr> | ||
+ | %for commutator in commutators: | ||
+ | Коммутатор: ${commutator.manufacturer} ${commutator.model} ${commutator.identify} | ||
+ | %for x in xrange(1,commutator.ports_count+1): | ||
+ | <tr> | ||
+ | <td>${x}</td><td>${subaccounts.get((commutator.id,x), "")}</td> | ||
+ | </tr> | ||
+ | %endfor | ||
+ | </table> | ||
+ | %endfor | ||
+ | </body> | ||
+ | </pre> | ||
+ | |||
+ | === Общий отчёт за предыдущий месяц === | ||
+ | <pre> | ||
+ | <html> | ||
+ | <head> | ||
+ | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | ||
+ | <style type="text/css"> | ||
+ | table.sample { | ||
+ | border-width: 1px; | ||
+ | border-spacing: 2px; | ||
+ | border-style: none; | ||
+ | border-color: black; | ||
+ | border-collapse: collapse; | ||
+ | background-color: white; | ||
+ | } | ||
+ | table.sample th { | ||
+ | border-width: 1px; | ||
+ | padding: 3px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | table.sample td { | ||
+ | border-width: 1px; | ||
+ | padding: 3px; | ||
+ | border-style: inset; | ||
+ | border-color: gray; | ||
+ | background-color: white; | ||
+ | -moz-border-radius: ; | ||
+ | } | ||
+ | </style> | ||
+ | </head> | ||
+ | <body> | ||
+ | <% | ||
+ | import os | ||
+ | import datetime | ||
+ | now = datetime.datetime.now()-datetime.timedelta(days=30) | ||
+ | accs = connection.get_models("billservice_account") | ||
+ | sp_start,sp_end,length=connection.sp_info(2, curdatetime=now) | ||
+ | i=0 | ||
+ | total_psh=0 | ||
+ | total_ash=0 | ||
+ | total_tr=0 | ||
+ | total_ttr=0 | ||
+ | total_total=0 | ||
+ | %> | ||
+ | <center><img src="${os.path.abspath("images/reports/header.png")}"></center> | ||
+ | <center><h3>Месячный отчёт с ${sp_start} по ${sp_end}</h3></center><br /> | ||
+ | <center><table class="sample"> | ||
+ | <th>№</th><th>Договор</th><th>ФИО</th><th>Тарифицировано трафика</th><th>Абонентская плата</th><th>Подключаемые услуги</th><th>Начислено</th><th>Итого списано</th> | ||
+ | % for acc in accs: | ||
+ | <% | ||
+ | traffictransaction=float("%.2f" % (connection.get("""SELECT sum(summ) as summ FROM billservice_traffictransaction | ||
+ | WHERE datetime between '%s' and '%s' and account_id=%s """% (sp_start, sp_end, acc.id)).summ or 0) ); | ||
+ | |||
+ | psh=float("%.2f" % (connection.get("""SELECT sum(summ) as summ FROM billservice_periodicalservicehistory AS psh | ||
+ | WHERE psh.datetime BETWEEN '%s' AND '%s' and psh.account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0)); | ||
+ | |||
+ | ash=float(str(connection.get("""SELECT sum(summ) as summ FROM billservice_addonservicetransaction WHERE created BETWEEN '%s' AND '%s' and account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0)); | ||
+ | tr=-1* (connection.get("""SELECT sum(summ) as summ FROM billservice_transaction WHERE summ<0 and created BETWEEN '%s' AND '%s' and account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0) | ||
+ | total=traffictransaction+psh+ash | ||
+ | total_psh+=psh | ||
+ | total_ash+=ash | ||
+ | total_tr+=tr | ||
+ | total_ttr+=traffictransaction | ||
+ | total_total+=total | ||
+ | %> | ||
+ | <tr> | ||
+ | <td>${i}</td><td>${acc.contract}</td><td>${acc.fullname}</td><td>${traffictransaction}</td> | ||
+ | <td>${psh}</td><td>${ash}</td><td>${tr}</td><td>${total}</td> | ||
+ | </tr> | ||
+ | <% i+=1 %> | ||
+ | %endfor | ||
+ | <td></td><td></td><td><strong>Итого:</strong></td><td><strong>${total_ttr}</strong></td><td><strong>${total_psh}</strong></td><td><strong>${total_ash}</strong></td><td><strong>${total_tr}</strong></td><td><strong>${total_total}</strong></td> | ||
+ | </table> | ||
+ | </center> | ||
+ | </body> | ||
+ | </html> | ||
+ | </pre> | ||
+ | |||
+ | == Документация по синтаксису шаблонов == | ||
+ | * http://www.makotemplates.org/ |
Текущая версия на 12:14, 9 ноября 2011
В ExpertBilling 1.4 внедрена принципиально новая система работы с отчётами. Сейчас вы сами сможете задать их внешний вид, а, также, создавать новые виды отчётов и документов. Из тела отчёта можно обратиться к rpc серверу, выполнить выборки из базы данных и отформатировать их в удобном для вас виде.
Свои отчёты вы можете сделать интерактивными. К примеру, перед его формированием, предложить пользователю указать необходимый период времени или выбрать другие параметры.
Для генерации отчётов необходимо создать шаблоны отчётов. Сделать это можно в меню "Главное меню"->"Шаблоны документов"
В левой части окна расположены категории отчётов. Часть отчётов предзадана и мы не рекомендуем их удалять. Чтобы создать новый отчёт необходимо выбрать первый пункт в дереве типов шаблонов "-- Новый шаблон --" и указать параметры шаблона справа. Выберите тип шаблона, его имя и задайте тело шаблона. С помощью кнопки "Сохранить" на панели инструментов сохраните его. Кнопка "Предпросмотр" служит для предварительного просмотра созданного шаблона
Содержание
- 1 Краткая информация по синтаксису шаблонов
- 2 Доступ к rpc серверу из отчёта
- 3 Пример отчёта
- 4 Отчёт по vpn сессиям абонентов
- 5 Встроенные документы
- 6 Отчёты
- 6.1 Отчёт по текущему списку активированных подключаемых услуг
- 6.2 Отчёт по списку активированных услуг за период
- 6.3 Отчёт по изменениям тарифного плана за период
- 6.4 Отчёт по списку занятых IP адресов
- 6.5 Отчёт по прибыли в день
- 6.6 Отчёт по прибыли с группировкой по месяцам
- 6.7 Занятые порты на коммутаторах
- 6.8 Общий отчёт за предыдущий месяц
- 7 Документация по синтаксису шаблонов
Краткая информация по синтаксису шаблонов
Обозначение переменных:
${value1} ${object.value}
Вызов функций и методов
${int(a)} ${object.method()}
Вычисления
${a+100} ${(1/2)+200**7}
Выполнение кода на языке python в теле отчёта
<% import datetime #получение текущего времени now = datetime.datetime.now() %>
Цикл for в теле отчёта
%for account in accounts: ${account.id}, ${account.username} %endfor
Получение списка доступных переменных и методов в объекте ${dir(object)}
Пример, демонстрирующий работу функции: Код:
<% #получаем учётную запись пользователя с id=100 account=connection.get_model(100, "billservice_account") %> ${dir(account)}
Результат: ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_asdict', '_fromTuple', '_toTuple', 'address', 'allow_expresscards', 'allow_ipn_with_block', 'allow_ipn_with_minus', 'allow_ipn_with_null', 'allow_vpn_block', 'allow_vpn_null', 'allow_webcab', 'assign_dhcp_block', 'assign_dhcp_null', 'assign_ipn_ip_from_dhcp', 'associate_pppoe_mac', 'associate_pptp_ipn_ip', 'balance_blocked', 'ballance', 'city', 'city_id', 'comment', 'contactperson', 'contactperson_phone', 'contract', 'created', 'credit', 'delete', 'disabled_by_limit', 'elevator_direction', 'email', 'entrance', 'entrance_code', 'fullname', 'get', 'hasattr', 'house', 'house_bulk', 'house_id', 'id', 'ipn_added', 'ipn_ip_address', 'ipn_ipinuse_id', 'ipn_mac_address', 'ipn_speed', 'ipn_status', 'isnull', 'last_balance_null', 'nas_id', 'passport', 'passport_date', 'passport_given', 'password', 'phone_h', 'phone_m', 'postcode', 'private_passport_number', 'region', 'room', 'row', 'save', 'status', 'street', 'street_id', 'suspended', 'systemuser_id', 'username', 'vlan', 'vpn_ip_address', 'vpn_ipinuse_id', 'vpn_speed']
Видим все доступные переменные из учётной записи пользователя. Чтобы вставить в тело отчёта телефонный номер абонента, нужно написать ${account.phone_m}, баланс - ${account.ballance} и т.д.
Доступ к rpc серверу из отчёта
В самом начале работы отчёта уже присутствует переменная connection, которая хранит в себе подключение к rpc серверу. Доступ к rpc серверу понадобится вам в том случае, если вы захотите получить доступ к данным, хранящимся в базе данных. Помимо доступа к данным вам будут доступны другие функции rpc сервера. Такие как сброс активной сессии на севрере доступа, активация/деактивация пользователя на сервере доступа и многие другие. Основные функции
- sql() - выполнение запроса. Функция возвращает результат вопроса в виде списка объектов записей
- get_model(id, table_name) - получить конкретную запись из указанной таблицы по её id
- get_models(table_name) - получить список записей из указанной таблицы
Пример:
<% accounts = connection.get_models("billservice_account") %> <table> <tr> <td>id</td><td>username</td> </tr> %for account in accounts: <tr> <td>${account.id}</td><td>${account.username}</td> </tr> %endfor </table>
В примере производится выборка всех аккаунтов и вывод их id и имён пользователей в виде таблицы.
Пример отчёта
Этот отчёт запрашивает период времени и потом показывает суммы, на которые был пополнен баланс у каждого пользователя.
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <% import os from CustomForms import PeriodForm child = PeriodForm(realm='ostatok') if child.exec_()!=1: return %> <% accounts = connection.get_models("billservice_account") %> <img src="${os.path.abspath("images/reports/header.png")}"> <center>Пополнения баланса за период <strong>С ${child.start_date}</strong> по <strong>${child.end_date}</strong></center> <table border=1 width=100%> <tr> <td>Имя пользователя</td><td>ФИО</td><td>Сумма</td> </tr> % for account in accounts: <% account_summ = connection.sql(r"SELECT sum(summ) FROM billservice_transaction WHERE account_id=%s and created between '%s' and '%s';" % (account.id, child.start_date, child.end_date))[0].sum or 0 %> <tr> <td>${account.username}</td><td>${account.fullname}</td><td>${account_summ}</td> </tr> % endfor </body> </html>
Отчёт по vpn сессиям абонентов
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <% import os from CustomForms import PeriodForm child = PeriodForm(realm='ostatok') if child.exec_()!=1: return %> <% sessions = connection.sql("""SELECT account_id, count(*) as sess_acount, (SELECT username FROM billservice_account as acc WHERE acc.id=account_id) as username, sum(bytes_in) as bytes_in, sum(bytes_out) as bytes_out FROM radius_activesession WHERE date_start between '%s' and '%s' GROUP BY account_id""" % (child.start_date, child.end_date)) %> <img src="${os.path.abspath("images/reports/header.png")}"> <center>Объём трафика по vpn сессиям<strong>С ${child.start_date}</strong> по <strong>${child.end_date}</strong></center> <table border=1 width=100%> <tr> <td>Имя пользователя</td><td>Принято</td><td>Отправлено</td><td>Количество сессий абонента</td> </tr> % for session in sessions: <tr> <td>${session.username}</td><td>${int((session.bytes_in or 0)/(1024*1024))} МБ</td><td>${int((session.bytes_out or 0)/(1024*1024))} МБ</td><td>${session.sess_acount}</td> </tr> % endfor </body> </html>
Результат:
Встроенные документы
В системе существуют следующие виды шаблонов:
- Договор на подключение физ.лица
- Договор на подключение юр.лица
- Накладная на карты
- Шаблон карты
- Кассовый чек
- Информационное письмо
- Акт выполненных работ
- Счёт-фактура
- Отчёты
Договор на подключение физ.лица
Система позволяет создать несколько шаблонов этого типа. В тело шаблона передаётся список из идентификаторов аккаунтов,если выбрана печать договоров для списка пользователей, или список, состоящий из одного элемента - id аккаунта, если печать производится из карточки аккаунта.
Доступные пременные:
accounts - тип список connection - подключение к rpc серверу
Пример шаблона:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <% import os provider = connection.get_operator()[0] %> <img src="${os.path.abspath("images/reports/header.png")}"> %for account_id in accounts: <% account = connection.get_model(account_id, "billservice_account") %> ID: ${account.id}<br> Договор номер ${account.contract}<br> %endfor </body> </html>
Для печати договора вам потребуется заполнить информацию о провайдере в меню Help->About Operator Получить эту информацию в шаблоне можно с помощью такой конструкции
provider = connection.get_operator()[0]
Список доступных полей у переменной provider:
id organization - название unp okpo contactperson director phone postaddress uraddress email fax bank_id
Получить информацию о банке можно с помощью конструкции
bank = connection.get_model(provide.bank_id, "billservice_bankdata")
Доступные переменные:
id bank bankcode rs
Договор на подключение юр.лица
Печать договора на подключение юр. лица ничем не отличается от физ. лиц за тем исключением, что для получения доступа к расширенному набору полей юрлиц вам необходимо воспользоваться конструкцией:
org = connection.get("SELECT * FROM billservice_organization WHERE account_id=%s" % account.id)
Доступные переменные:
id account_id name uraddress okpo unp bank_id phone fax kpp kor_s
Получить банк вы можете способом, описанным выше в шаблоне для физ. лиц.
Накладная на карты
Этот тип документа описывает факт передачи партии карт доступа/экспресс-оплаты/HotSpot дилеру. В шаблон передаётся список объектов карт в переменной cards, объект с информацией о провайдере в переменной operator, объект дилера в переменной dealer, текущая дата в переменной created, cardcount - количество продаваемых карт, sum_for_pay - сумма к оплате, pay - сколько оплачено, discount - процент скидки, discount_sum - сумма скидки, paydeffer - отсрочка оплаты в днях.
Вид шаблона по-умолчанию:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body> <div style="width:100%; "> <div style="float:right "> <span style="font-weight:bold; ">Дилер</span><br> Организация: ${dealer.organization}<br> Директор: ${dealer.director}<br> Юр адрес: ${dealer.uraddress}<br> р/с: ${dealer.rs}<br> УНН: ${dealer.unp}<br> ОКПО: ${dealer.okpo}<br> Банк: ${dealer.bank}, код ${dealer.bankcode}<br> </div> <div style="float:left "> <span style="font-weight:bold; ">Оператор</span><br> Организация: ${operator.organization}<br> Директор: ${operator.director}<br> Юр адрес: ${operator.uraddress}<br> р/с: ${operator.rs} <br> УНН: ${operator.unp}<br> ОКПО: ${operator.okpo}<br> Банк: ${operator.bank}, Код ${operator.bankcode}<br> </div> </div> <div style="font-weight:bold; float:left; width:100%; text-align:center; margin-bottom:20px; margin-top:20px; "> Накладная от ${created} </div> <div style="clear:both "></div> <table border="1" align="center" style="width:100%"> <tr> <td>ID карты</td> <td>Серия</td> <td>Номинал</td> <td>Активировать С</td> <td>Активировать По</td> </tr> % for card in cards: <tr> <td>${card.id}</td> <td>${card.series}</td> <td>${card.nominal}</td> <td>${card.start_date}</td> <td>${card.end_date}</td> </tr> % endfor </table> Итого ${cardcount} карт на сумму: ${sum_for_pay}<br> Скидка: ${discount} на сумму ${discount_sum}<br> Оплачено: ${pay}<br> Оплатить до:${paydeffer} </body> </html>
Результат работы шаблона:
Шаблон карты
Шаблоны карт предоплаты используется при печати карт в момент передачи их дилеру. Учитывая то, что в одной поставке могут оказаться карты разных типов, номиналов и шаблонов, шаблон карты должен описывать только её внешний вид. Изначально в шаблон передаётся подключение к rpc серверу с переменной connection, переменная operator с объектом, содержащим информацию о провайдере, список карт в переменной cards.
Каждый объект типа card содержит следующие переменные:
id series pin sold nominal start_date end_date disabled created template_id account_id tarif_id nas_id login ip ipinuse_id
Предустановлен следующий шаблон карты по-умолчанию:
<div> <div>Карта пополнения на ${card.nominal} рублей</div> <div>Для пополнения счета зайдите на сайт http://192.168.12.2 и введите свой логин и пароль. Далее нажмите "Активация карточки". В появившемся окне введите: </div> <div>Серия: ${card.series}</div> <div>ID: ${card.id}</div> <div>PIN: ${card.pin}</div> <div>Активировать до ${card.end_date} </div> </div>
При печати к началу документа подставляется стандартный блок
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body>
и
</body></html>
В конце сгенерированного файла. Таким образом не нарушается структура html документа.
Важно!!! Если вы планируете использовать в шаблонах карт картинки - указывайте полный путь к картинке на файловой системе или пользуйтесь конструкцией
<% import os %> - поместить 1 раз в начале файла. <img src="${os.path.abspath("images/reports/header.png")}">
Где images/reports/header.png - путь к файлу с картинкой относительно папки с интерфейсом администратора.
Кассовый чек
Шаблон кассового чека используется при печати чеков из интерфейса кассира и интерфейса администратора. Важно!!! Не создавайте больше 1 шаблона кассового чека. В шаблон чека передаётся переменная connection, содержащая подключение к базе данных, переменная transaction с выполненным платежом, переменная account с пользователем, которому пополняли баланс.
По-умолчанию шаблон чека выглядит следующим образом:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <style> td{ FONT: 9px Times New Roman; } h1{ FONT: 9px Arial; } </style> </head> <body> <% tr=connection.get("SELECT id FROM billservice_transaction WHERE created='%s'" % transaction.created) tarif_name = connection.get(" SELECT get_tarif_name(%s, now()::timestamp without time zone)" % account.id).get_tarif_name %> <table align=center width="85%"> <tr> <td> <h1 align=center> <b> Квитанция об оплате услуг № ${tr.id} </b> </h1> <strong>Абонент:</strong> ${account.fullname} <br> <strong>Тарифный план:</strong> ${tarif_name} <br> <strong>Логин:</strong> ${account.username}<br> <strong>Сумма:</strong> ${transaction.summ}<br> <strong>Дата приема платежа:</strong> ${transaction.created}<br> </td> </tr> </table> </body> </html>
Доступные переменные в transaction:
bill account_id type_id approved tarif_id summ description created systemuser_id promise end_promise promise_expired accounttarif_id
Обратите внимание, что в базе данных пополнения хранятся с минусом, а списания с плюсом. Поэтому при выводе в чеке суммы, её нужно умножить на -1. ${-1*transaction.summ} Если вы хотите дополнить шаблон чека информацией о кассире, выполнившем платёж - его можно получить следующим выражением: <% user=connection.get_model(transaction.systemuser_id, "billservice_systemuser") %> Доступные переменные системного пользователя:
id username password last_ip last_login description created status host role text_password email job fullname address home_phone mobile_phone passport passport_details passport_number unp im
Информационное письмо
Информационное письмо используется при рассылке уведомлений о приближении баланса к нулю. Рассылку осуществляет скрипт sendmail.py, находящийся в папке с биллингом. Настройка скрипта производится в файле ebs_config.ini
Шаблон письма по-умолчанию:
--------------------------------------------------- Это сообщение сгенерировано биллинговой системой! --------------------------------------------------- Здравствуйте, ${account.username}. Уведомляем, что актуальный баланс Вашего лицевого счета составляет ${"%.2f" % account.ballance} руб. Размер кредита ${account.credit}. Пожалуйста, пополните баланс во избежание блокировки. --- ${operator.organization}
Акт выполненных работ
Счёт-фактура
Отчёты
Отчёт по текущему списку активированных подключаемых услуг
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; font-size:10pt; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% accountaddonservices=connection.sql("""select (select name from billservice_addonservice WHERE id=aas.service_id) as name, (select username from billservice_account WHERE id=aas.account_id) as username, (select username from billservice_subaccount WHERE id=aas.subaccount_id) as subacc_username, activated, temporary_blocked, (select cost FROM billservice_addonservice WHERE id=aas.service_id) as cost from billservice_accountaddonservice as aas WHERE deactivated is not Null ORDER BY activated; """) cost=0 %> <div> <strong>Активные подключаемые услуги:</strong></div> <table class="sample" width="100%"> <tr> <td>Название услуги</td><td>Цена</td><td>Имя аккаунта</td><td>Имя субаккаунта</td><td>Дата активации</td><td>Блокировка</td> </tr> %for aas in accountaddonservices: <% cost+=aas.cost %> <tr> <td>${aas.name}</td><td>${aas.cost}</td><td>${aas.username}</td><td>${aas.subacc_username if aas.subacc_username else ''}</td><td>${aas.activated}</td><td>${aas.temporary_blocked if aas.temporary_blocked else u'Нет'}</td> </tr> %endfor <tr> <td></td><td></td><td>Итого: ${cost}</td><td></td><td></td><td></td> </tr> </table> </body> </html>
Пример работы:
Отчёт по списку активированных услуг за период
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <% import os from CustomForms import PeriodForm child = PeriodForm(realm=u'Период') if child.exec_()!=1: return %> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; font-size:10pt; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% accountaddonservices=connection.sql("""select (select name from billservice_addonservice WHERE id=aas.service_id) as name, (select username from billservice_account WHERE id=aas.account_id) as username, (select username from billservice_subaccount WHERE id=aas.subaccount_id) as subacc_username, activated, deactivated, (select cost FROM billservice_addonservice WHERE id=aas.service_id) as cost from billservice_accountaddonservice as aas WHERE activated between '%s' and '%s' ORDER BY activated; """ % (child.start_date, child.end_date,)) cost=0 %> <div> <strong>Активированные подключаемые услуги:</strong></div> <table class="sample" width="100%"> <tr> <td>Название услуги</td><td>Цена</td><td>Имя аккаунта</td><td>Имя субаккаунта</td><td>Дата активации</td><td>Дата деактивации</td> </tr> %for aas in accountaddonservices: <% cost+=aas.cost %> <tr> <td>${aas.name}</td><td>${aas.cost}</td><td>${aas.username}</td><td>${aas.subacc_username if aas.subacc_username else ''}</td><td>${aas.activated}</td><td>${aas.deactivated if aas.deactivated else ''}</td> </tr> %endfor <tr> <td></td><td></td><td>Итого: ${cost}</td><td></td><td></td><td></td> </tr> </table> </body> </html>
Пример работы:
Отчёт по изменениям тарифного плана за период
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body font-size='10pt'> <% import os from CustomForms import PeriodForm child = PeriodForm(realm=u'Период') if child.exec_()!=1: return %> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; font-size: 10pt; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% accounttarifs=connection.sql("""SELECT acc.username, tarif.name, act.datetime, act.periodical_billed, (SELECT name FROM billservice_tariff WHERE id=(SELECT tarif_id FROM billservice_accounttarif WHERE account_id=acc.id and datetime<act.datetime ORDER BY datetime DESC LIMIT 1)) as prev_tarif FROM billservice_accounttarif as act JOIN billservice_account as acc on acc.id=act.account_id JOIN billservice_tariff as tarif ON tarif.id=act.tarif_id WHERE act.datetime between '%s' and '%s' ORDER BY act.datetime """ % (child.start_date, child.end_date,)) strftimeFormat = "%d.%m.%Y %H:%M:%S" %> <div> <strong>Изменения тарифного плана:</strong></div> <table class="sample" width="100%"> <tr> <td>Имя аккаунта</td><td>Название тарифа</td><td>Дата начала</td><td>Период закрыт</td><td>Предыдущий тариф</td> </tr> %for act in accounttarifs: <tr> <td>${act.username}</td><td>${act.name}</td><td>${act.datetime.strftime(strftimeFormat)}</td><td>${act.periodical_billed}</td><td>${act.prev_tarif if act.prev_tarif else ''}</td> </tr> %endfor </table> </body> </html>
Пример работы
Отчёт по списку занятых IP адресов
Простой отчёт.
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% pools=connection.sql("""SELECT id, name FROM billservice_ippool""") count=0 %> <div> <strong>Занятые IP адреса в пулах:</strong></div> %for pool in pools: <strong>${pool.name}</strong><br/> <% ips=connection.sql("""SELECT DISTINCT ip FROM billservice_ipinuse WHERE disabled is Null and ip!='0.0.0.0' and pool_id=%s ORDER BY ip ASC""" % pool.id) connection.commit() %> %for ip in ips: ${ip.ip} <br/> <% count+=1 %> %endfor %endfor <tr> <td></td><td></td><td>Итого: ${count}</td><td></td><td></td><td></td> </tr> </table> </body> </html>
Пример работы
Отчёт по прибыли в день
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body font-size='10pt'> <% import os from CustomForms import PeriodForm child = PeriodForm(realm=u'Период') if child.exec_()!=1: return %> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% transactions=connection.sql("""SELECT sum(summ*-1), date_part('day',created) as day, date_part('month',created) as month, date_part('year',created) as year FROM billservice_transaction WHERE created between '%s' and '%s' GROUP BY date_part('year',created),date_part('month',created),date_part('day',created) ORDER BY year,month, day ASC; """ % (child.start_date, child.end_date,)) cost=0 %> <div> <strong>Отчёт по прибыли с группировкой по дням:</strong></div> <table class="sample" width="100%"> <tr> <td>Год</td><td>Месяц</td><td>День</td><td>Сумма</td> </tr> %for transaction in transactions: <% cost+=transaction.sum %> <tr> <td>${int(transaction.year)}</td><td>${int(transaction.month)}</td><td>${int(transaction.day)}</td><td>${transaction.sum if transaction.sum else ''}</td> </tr> %endfor <tr> <td></td><td></td><td><strong>Итого</strong></td><td> ${cost}</td> </tr> </table> </body> </html>
Пример работы
Отчёт по прибыли с группировкой по месяцам
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body font-size='10pt'> <% import os from CustomForms import PeriodForm child = PeriodForm(realm=u'Период') if child.exec_()!=1: return %> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os import datetime from dateutil.relativedelta import relativedelta now = datetime.datetime.now() %> <img src="${os.path.abspath("images/reports/header.png")}"> <% transactions=connection.sql("""SELECT sum(summ*-1), date_part('month',created) as month, date_part('year',created) as year FROM billservice_transaction WHERE created between '%s' and '%s' GROUP BY date_part('year',created),date_part('month',created) ORDER BY year,month ASC; """ % (child.start_date, child.end_date,)) cost=0 %> <div> <strong>Отчёт по прибыли с группировкой по дням:</strong></div> <table class="sample" width="100%"> <tr> <td>Год</td><td>Месяц</td><td>Сумма</td> </tr> %for transaction in transactions: <% cost+=transaction.sum %> <tr> <td>${int(transaction.year)}</td><td>${int(transaction.month)}</td><td>${transaction.sum if transaction.sum else ''}</td> </tr> %endfor <tr> <td></td><td><strong>Итого</strong></td><td> ${cost}</td> </tr> </table> </body> </html>
Пример работы
Занятые порты на коммутаторах
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; } table.sample th { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: gray; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 1px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> <% import os commutators=connection.get_models("nas_switch") subaccounts_data=connection.sql("""SELECT acc.username, subacc.switch_id, subacc.switch_port FROM billservice_account as acc JOIN billservice_subaccount as subacc ON subacc.account_id=acc.id WHERE acc.id in (SELECT account_id FROM billservice_subaccount WHERE switch_id is not Null and switch_port is not Null)""") subaccounts={} %> %for subacc in subaccounts_data: <% subaccounts[(subacc.switch_id,subacc.switch_port)]=subacc.username %> %endfor <img src="${os.path.abspath("images/reports/header.png")}"> <div> <strong>Отчёт по занятым портам коммутаторов:</strong></div> <table class="sample" width="100%"> <tr> <th>Порт</th><th>Аккаунт</th> </tr> %for commutator in commutators: Коммутатор: ${commutator.manufacturer} ${commutator.model} ${commutator.identify} %for x in xrange(1,commutator.ports_count+1): <tr> <td>${x}</td><td>${subaccounts.get((commutator.id,x), "")}</td> </tr> %endfor </table> %endfor </body>
Общий отчёт за предыдущий месяц
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <style type="text/css"> table.sample { border-width: 1px; border-spacing: 2px; border-style: none; border-color: black; border-collapse: collapse; background-color: white; } table.sample th { border-width: 1px; padding: 3px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } table.sample td { border-width: 1px; padding: 3px; border-style: inset; border-color: gray; background-color: white; -moz-border-radius: ; } </style> </head> <body> <% import os import datetime now = datetime.datetime.now()-datetime.timedelta(days=30) accs = connection.get_models("billservice_account") sp_start,sp_end,length=connection.sp_info(2, curdatetime=now) i=0 total_psh=0 total_ash=0 total_tr=0 total_ttr=0 total_total=0 %> <center><img src="${os.path.abspath("images/reports/header.png")}"></center> <center><h3>Месячный отчёт с ${sp_start} по ${sp_end}</h3></center><br /> <center><table class="sample"> <th>№</th><th>Договор</th><th>ФИО</th><th>Тарифицировано трафика</th><th>Абонентская плата</th><th>Подключаемые услуги</th><th>Начислено</th><th>Итого списано</th> % for acc in accs: <% traffictransaction=float("%.2f" % (connection.get("""SELECT sum(summ) as summ FROM billservice_traffictransaction WHERE datetime between '%s' and '%s' and account_id=%s """% (sp_start, sp_end, acc.id)).summ or 0) ); psh=float("%.2f" % (connection.get("""SELECT sum(summ) as summ FROM billservice_periodicalservicehistory AS psh WHERE psh.datetime BETWEEN '%s' AND '%s' and psh.account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0)); ash=float(str(connection.get("""SELECT sum(summ) as summ FROM billservice_addonservicetransaction WHERE created BETWEEN '%s' AND '%s' and account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0)); tr=-1* (connection.get("""SELECT sum(summ) as summ FROM billservice_transaction WHERE summ<0 and created BETWEEN '%s' AND '%s' and account_id=%s;""" % (sp_start, sp_end, acc.id)).summ or 0) total=traffictransaction+psh+ash total_psh+=psh total_ash+=ash total_tr+=tr total_ttr+=traffictransaction total_total+=total %> <tr> <td>${i}</td><td>${acc.contract}</td><td>${acc.fullname}</td><td>${traffictransaction}</td> <td>${psh}</td><td>${ash}</td><td>${tr}</td><td>${total}</td> </tr> <% i+=1 %> %endfor <td></td><td></td><td><strong>Итого:</strong></td><td><strong>${total_ttr}</strong></td><td><strong>${total_psh}</strong></td><td><strong>${total_ash}</strong></td><td><strong>${total_tr}</strong></td><td><strong>${total_total}</strong></td> </table> </center> </body> </html>