BILLmanager contains the most typical and widely used reports, but this may not be enough. This article explains how to create your own report.
For this, you need SQL knowledge, as well as an understanding of the database structure in BILLmanager. If the report is complex and cannot be built with a single database query, you will also need programming skills in any language you know.
Simple report
Report development consists of the following steps:
- Adding the report to the main menu or to the report list.
- Describing the form for entering parameters, if needed.
- Describing the report layout.
- Building the SQL query to retrieve data.
The report definition, like any other plugin, is created using the XML file /usr/local/mgr5/etc/xml/<mgr>_mod_<name>.xml, where <mgr> is the product name and <name> is the plugin name. For example, the plugin for BILLmanager is called myreport. Then the file should be named /usr/local/mgr5/etc/xml/billmgr_mod_myreport.xml.
Example of adding a link to the report in the Statistics section in the left menu. The report shows a list of the provider's companies.
Example of adding a link
<mgrdata>
<mainmenu level="29">
<node name="stat">
<node name="myreport"/>
</node>
</mainmenu>
<metadata name="myreport" type="report">
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
</band>
</metadata>
<lang name="en">
<messages name="desktop">
<msg name="menu_myreport">My first report</msg>
</messages>
<messages name="myreport">
<msg name="title">Title of my first report</msg>
</messages>
</lang>
</mgrdata>
Nested data
To display a list of payment methods accepted by each company in the report, add a nested band to the definition:
Example of adding a band
<metadata name="myreport" type="report">
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.name_ru as paymethod from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]]</query>
<col name="paymethod" type="data"/>
</band>
</band>
</metadata>
For example, the report may need to display only payment methods whose minimum payment amount is greater than a certain value. To do this, add a form and a parameter to the SQL query. To prevent the report from running automatically with undefined parameters, stop it from being built until the form is filled in: add the firstrun="no" attribute.
Note that text inside the XML file must be escaped. For example:
Example without escaping
select p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[minamount]]
should be written like this:
Example with escaping
select p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[minamount]]
Example
Example of a form with escaped characters
<metadata name="myreport" type="report" firstrun="no">
<form>
<field name="minamount">
<input type="text" name="minamount" save="yes" required="yes" check="int"/>
</field>
</form>
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[minamount]]</query>
<col name="paymethod" type="data"/>
<col name="minamount" type="data" sort="digit"/>
</band>
</band>
</metadata>
Navigation from reports to other modules
To navigate from the report to the payment method edit form, add the payment method id column with the nestedreport="paymethod.edit" attribute. The "nestedreport="paymethod.edit" attribute creates a link that opens a new tab with the paymethod.edit function. The following values will be passed to the function as keys:
- the payment method id value;
- all form fields.
The paymethod.edit function has its own parameter minamoun t. Therefore to prevent it from being replaced by a parameter from the form, rename the parameter in the form to repminamount.
Example of the paymethod.edit function
<metadata name="myreport" type="report" firstrun="no">
<form>
<field name="repminamount">
<input type="text" name="repminamount" save="yes" required="yes" check="int"/>
</field>
</form>
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.id, p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[repminamount]]</query>
<col name="id" type="data" nestedreport="paymethod.edit"/>
<col name="paymethod" type="data"/>
<col name="minamount" type="data" sort="digit"/>
</band>
</band>
</metadata>
Navigation to a linked report
To add navigation to a linked report, specify the name of the report you want to open as the value of the nestedreport attribute.
To create a report that displays payment statistics in different statuses for a specific payment method:
-
In the existing report, change the description of the linked column:
Example of column description
<col name="id" type="data" nestedreport="myreport.detail"/>
-
Add the definition of the new report:
Example of report description
<metadata name="myreport.detail" type="report">
<band name="payments">
<query>select status, sum(paymethodamount) as amount, count(*) as cnt from payment where paymethod=[[elid]] group by status</query>
<col name="status" type="msg"/>
<col name="amount" type="data" convert="money" sort="digit" total="sum"/>
<col name="cnt" type="data" sort="digit" total="sum"/>
</band>
</metadata>
-
Add a message section for the new report:
Example of a message section
<messages name="myreport.detail">
<msg name="title">Payment method statistics</msg>
<msg name="status_1">New</msg>
<msg name="status_4">Credited</msg>
<msg name="status">Status</msg>
<msg name="cnt">Amount</msg>
</messages>
Note that the status codes were converted to their names.
Adding charts and graphs
To display a chart in the report, add the following line to the band:
<diagram name="statuspie" label="status" data="amount" type="pie"/>
Example of a report with a chart
<mgrdata>
<mainmenu level="29">
<node name="stat">
<node name="myreport"/>
</node>
</mainmenu>
<metadata name="myreport" type="report" firstrun="no">
<form>
<field name="repminamount">
<input type="text" name="repminamount" save="yes" required="yes" check="int"/>
</field>
</form>
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.id, p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[repminamount]]</query>
<col name="id" type="data" nestedreport="myreport.detail"/>
<col name="paymethod" type="data"/>
<col name="minamount" type="data" sort="digit"/>
</band>
</band>
</metadata>
<metadata name="myreport.detail" type="report">
<band name="payments">
<diagram name="statuspie" label="status" data="amount" type="pie"/>
<query>select status, sum(paymethodamount) as amount, count(*) as cnt from payment where paymethod=[[elid]] group by status</query>
<col name="status" type="msg"/>
<col name="amount" type="data" convert="money" sort="digit" total="sum"/>
<col name="cnt" type="data" sort="digit" total="sum"/>
</band>
</metadata>
<lang name="en">
<messages name="desktop">
<msg name="menu_myreport">My first report</msg>
</messages>
<messages name="myreport">
<msg name="title">Title of my first report</msg>
<msg name="paymethod">Payment method</msg>
<msg name="repminamount">Minimum payment</msg>
<msg name="hint_repminamount">Show only payment methods where the minimum payment is greater than the specified value</msg>
</messages>
<messages name="myreport.detail">
<msg name="title">Payment method statistics</msg>
<msg name="status_1">New</msg>
<msg name="status_4">Credited</msg>
<msg name="status">Status</msg>
<msg name="cnt">Amount</msg>
</messages>
</lang>
</mgrdata>
Generating data with a script
Not all reports can be built using only an XML definition.
The main reasons why handler scripts are needed:
- building a report from data stored outside the database, for example, a file list, output from external services, and so on;
- dynamic SQL generation based on input parameters;
- dynamic building of the report structure, set of columns, additional statistics rows, and so on;
- complex data structure that is hard to retrieve with a single query.
Structure of the output data that the handler must generate
The data must contain the reportdata tag, then at the next nesting level there is the band name tag (in our example, this is company). Several bands can be placed one after another in a single report. In each band, a separate data row is represented by the elem tag, where each column corresponds to a tag named after the column. In the example script from the article, these are id, name, and so on.
For each nested band, data is added in the same way, i.e. the band name, rows (elem), columns. Any number of nested bands can be used, but we do not recommend more than three, because reports become difficult to understand.
Example with two band name tags
<doc>
<reportdata>
<company>
<elem>
<id>1</id>
<name>company 1</name>
<paymethod>
<elem>
<id>1</id>
<paymethod>Bank transfer company 1</paymethod>
<minamount>100.0000</minamount>
</elem>
<elem>
<id>2</id>
<paymethod>WebMoney (WMR)</paymethod>
<minamount>0.0000</minamount>
</elem>
</paymethod>
</elem>
<elem>
<id>2</id>
<name>company 2</name>
<paymethod>
<elem>
<id>2</id>
<paymethod>WebMoney (WMR)</paymethod>
<minamount>0.0000</minamount>
</elem>
</paymethod>
</elem>
</company>
</reportdata>
</doc>
Text descriptions
To make the report clearer, we recommend adding a description and labels for its different blocks. Use text messages for this (the msg tags from messages) with predefined names:
report_info — report description;table_[BANDNAME] — table description with data;diagram_[DIAGRAMNAME] — chart description.
Embedding the report into the standard report list
To add your custom reports to the left menu Reports section, follow these rules:
- the report name must start with the
report. prefix; - the
metadata tag must contain the group attribute.
Example of a meta tag
<metadata name="report.myreport" type="report" firstrun="no" group="mygroup">
You can use your own name for the group, or add the report to one of the existing ones:
- finance;
- account;
- item;
- marketing;
- support.
You can localize messages:
Example of localization
<messages name="reportlist">
<msg name="report_mygroup">My reports</msg>
<msg name="report_myreport">My first report</msg>
</messages>
Access rights
By default, all authenticated users can run any plugins. Even if a plugin is not in the interface menu, it is available through the API.
To make the report available only to users with administrator rights, add the level attribute to the metadata tag:
Example of a line with the level attribute
<metadata name="report.myreport" type="report" firstrun="no" group="mygroup" level="admin+">
Now the report is available only to administrators with full rights, as well as the root user. For all other employees or departments, access can be granted through the standard rights assignment interface.
Report plugin
The plugin, which is embedded into the left menu as a separate item and has no access rights restrictions:
Example plugin
<mgrdata>
<mainmenu level="29">
<node name="stat">
<node name="myreport"/>
</node>
</mainmenu>
<metadata name="myreport" type="report" firstrun="no">
<form>
<field name="repminamount">
<input type="text" name="repminamount" save="yes" required="yes" check="int"/>
</field>
</form>
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.id, p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[repminamount]]</query>
<col name="id" type="data" nestedreport="myreport.detail"/>
<col name="paymethod" type="data"/>
<col name="minamount" type="data" sort="digit"/>
</band>
</band>
</metadata>
<metadata name="myreport.detail" type="report">
<band name="payments">
<diagram name="statuspie" label="status" data="amount" type="pie"/>
<query>select status, sum(paymethodamount) as amount, count(*) as cnt from payment where paymethod=[[elid]] group by status</query>
<col name="status" type="msg"/>
<col name="amount" type="data" convert="money" sort="digit" total="sum"/>
<col name="cnt" type="data" sort="digit" total="sum"/>
</band>
</metadata>
<lang name="en">
<messages name="desktop">
<msg name="menu_myreport">My first report</msg>
</messages>
<messages name="myreport">
<msg name="title">Title of my first report</msg>
<msg name="paymethod">Payment method</msg>
<msg name="repminamount">Minimum payment</msg>
<msg name="hint_repminamount">Show only payment methods where the minimum payment is greater than the specified value</msg>
</messages>
<messages name="myreport.detail">
<msg name="title">Payment method statistics</msg>
<msg name="status_1">New</msg>
<msg name="status_4">Credited</msg>
<msg name="status">Status</msg>
<msg name="cnt">Amount</msg>
</messages>
</lang>
</mgrdata>
The plugin taking into account renaming the report and moving it from the main menu to the report list:
Example plugin
<mgrdata>
<metadata name="report.myreport" type="report" firstrun="no" group="mygroup" level="admin+">
<form>
<field name="repminamount">
<input type="text" name="repminamount" save="yes" required="yes" check="int"/>
</field>
</form>
<band name="company">
<query>select id, name from profile where account=1</query>
<col name="name" type="data"/>
<band name="paymethod">
<query>select p.id, p.name_ru as paymethod, minamount from paymethod2company pc left join paymethod p on pc.paymethod=p.id where pc.company=[[company.id]] and p.minamount>=[[repminamount]]</query>
<col name="id" type="data" nestedreport="myreport.detail"/>
<col name="paymethod" type="data"/>
<col name="minamount" type="data" sort="digit"/>
</band>
</band>
</metadata>
<metadata name="myreport.detail" type="report" level="admin+">
<band name="payments">
<diagram name="statuspie" label="status" data="amount" type="pie"/>
<query>select status, sum(paymethodamount) as amount, count(*) as cnt from payment where paymethod=[[elid]] group by status</query>
<col name="status" type="msg"/>
<col name="amount" type="data" convert="money" sort="digit" total="sum"/>
<col name="cnt" type="data" sort="digit" total="sum"/>
</band>
</metadata>
<lang name="en">
<messages name="reportlist">
<msg name="report_mygroup">My reports</msg>
<msg name="report_myreport">My first report</msg>
</messages>
<messages name="report.myreport">
<msg name="title">Title of my first report</msg>
<msg name="paymethod">Payment method</msg>
<msg name="repminamount">Minimum payment</msg>
<msg name="hint_repminamount">Show only payment methods where the minimum payment is greater than the specified value</msg>
</messages>
<messages name="myreport.detail">
<msg name="title">Payment method statistics</msg>
<msg name="status_1">New</msg>
<msg name="status_4">Credited</msg>
<msg name="status">Status</msg>
<msg name="cnt">Amount</msg>
</messages>
</lang>
</mgrdata>
The article was last updated on July 02, 2026. The article was prepared by technical writers of ISPsystem