Создание отчётов — различия между версиями

Материал из ExpertBilling
Перейти к: навигация, поиск
(Отчёты)
(Отчёт по списку активированных услуг за период)
Строка 582: Строка 582:
  
 
=== Отчёт по списку активированных услуг за период===
 
=== Отчёт по списку активированных услуг за период===
 +
<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;
 +
}
 +
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>
  
 
== Документация по синтаксису шаблонов ==
 
== Документация по синтаксису шаблонов ==
 
* http://www.makotemplates.org/
 
* http://www.makotemplates.org/

Версия 11:32, 29 августа 2011

В ExpertBilling 1.4 внедрена принципиально новая система работы с отчётами. Сейчас вы сами сможете задать их внешний вид, а, также, создавать новые виды отчётов и документов. Из тела отчёта можно обратиться к rpc серверу, выполнить выборки из базы данных и отформатировать их в удобном для вас виде.

Свои отчёты вы можете сделать интерактивными. К примеру, перед его формированием, предложить пользователю указать необходимый период времени или выбрать другие параметры.

Reports.png

Для генерации отчётов необходимо создать шаблоны отчётов. Сделать это можно в меню "Главное меню"->"Шаблоны документов"

В левой части окна расположены категории отчётов. Часть отчётов предзадана и мы не рекомендуем их удалять. Чтобы создать новый отчёт необходимо выбрать первый пункт в дереве типов шаблонов "-- Новый шаблон --" и указать параметры шаблона справа. Выберите тип шаблона, его имя и задайте тело шаблона. С помощью кнопки "Сохранить" на панели инструментов сохраните его. Кнопка "Предпросмотр" служит для предварительного просмотра созданного шаблона

Краткая информация по синтаксису шаблонов

Обозначение переменных:

${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>
Period1.png

Отчёт по 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>

Результат:

Report sessions.png

Встроенные документы

В системе существуют следующие виды шаблонов:

  • Договор на подключение физ.лица
  • Договор на подключение юр.лица
  • Накладная на карты
  • Шаблон карты
  • Кассовый чек
  • Информационное письмо
  • Акт выполненных работ
  • Счёт-фактура
  • Отчёты

Договор на подключение физ.лица

Система позволяет создать несколько шаблонов этого типа. В тело шаблона передаётся список из идентификаторов аккаунтов,если выбрана печать договоров для списка пользователей, или список, состоящий из одного элемента - 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>

Результат работы шаблона:

Sale card.png

Шаблон карты

Шаблоны карт предоплаты используется при печати карт в момент передачи их дилеру. Учитывая то, что в одной поставке могут оказаться карты разных типов, номиналов и шаблонов, шаблон карты должен описывать только её внешний вид. Изначально в шаблон передаётся подключение к 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>
  <table align=center width="85%">
    <tr>
     <td>
       <h1 align=center> <b> Квитанция об оплате услуг № ${transaction.id} </b> </h1>
       <strong>Абонент:</strong> ${account.fullname} <br>
       <strong>Логин:</strong> ${account.username}<br>
       <strong>Сумма:</strong> ${-1*transaction.summ}<br>
       <strong>Дата приема платежа:</strong> ${transaction.created}<br>
       ${connection.get_model(account.id, "billservice_account").fullname}
    </td>
   </tr>
  </table>
 </body>
</html>

Доступные переменные в transaction:

id
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;
}
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>

Пример работы:

Activated addonservices.png

Отчёт по списку активированных услуг за период

<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; } 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 %>

Активированные подключаемые услуги:
%for aas in accountaddonservices: <% cost+=aas.cost %> %endfor
Название услугиЦенаИмя аккаунтаИмя субаккаунтаДата активацииДата деактивации
${aas.name}${aas.cost}${aas.username}${aas.subacc_username if aas.subacc_username else }${aas.activated}${aas.deactivated if aas.deactivated else }
Итого: ${cost}

</body> </html>

Документация по синтаксису шаблонов