免费开源ERP Odoo如何开发BI报表

创建商业报表

通常我们会在我们的To Do模块中实现报表。但是为了学习目的,我们将为我们的报表创建一个新的addon模块。

我们的报表效果如下:

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

我们将命名这个新的addon模块为todo_report。首先要做的是创建一个空的__init__.py文件和__manifest__.py文件:

(
'name': 'To–Do Report',
'description': 'Report for To–Do tasks.', 'author': 'Daniel Reis',
'depends': ['todo_kanban'],
'data': ['reports/todo_report.xml'] }
reports/todo_report.xml文件可以通过声明以下内容开始:
<?xml version="l.O"?>
<odoo>
<report id="action_todo_task_report" string="To–do Tasks" model="todo.task" report_type="qweb–pdf"
name="todo_report.report_todo_task_template"
/>
</odoo>

<report>标签是将数据写入到ir.actions.report.xml模型的快捷方式。这个模型代表一种特定类型的客户端动作。可以在Settings Technical Reports菜单选项中浏览这个模型的数据。

在报告的设计过程中,最好先设置报表类型report_type="qweb–html",并在完成报表设计后将其修改回qweb-pdf文件。这样可以更快地生成报表,也更容易检查来自OWeb模板的HTML结果。

在安装了这个之后,待办任务表单视图将在顶部在More按钮的左边,显示一个Print按钮,其中包含执行报表打印的选项。

因为我们还没有定义报表,所以现在无法使用。这将是一个QWeb报表,因此它需要一个QWeb模板。name属性可以标识将使用的模板。与其他标识符引用不同,name属性中的模块前缀是必需的。我们必须充分参考<module_name>.<identifier_name>。

QWeb报表模板

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

报表通常遵循基本框架,如下所示。这可以添加到reports/todo_report.xml文件,在<report>元素之后。

<template id="report_todo_task_template">
<t t–call="report.html_container">
<t t–call="report.external_layout">
<div class="page">
<!–– Report page content ––>
</div>
</t>
</t>
</template>

这里最重要的元素是使用标准报表结构的t-call指令。report.html_container模板将运行支持HTML文档的基本设置。report.external_layout模板,将使用公司的相应设置,处理报表页眉和页脚。作为另一种选择,我们可以使用report.internal_layout模板,它只使用一个基本页眉。

现在,我们有了模块和报表视图的基本框架。请注意,由于报表是QWeb模板,所以就像其他视图一样可以使用继承。在报表中使用的QWeb模板,可以使用XPATH表达式进行常规的继承视图扩展。

报表中的数据

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

与看板视图不同,报表中的QWeb模板是在服务器端渲染的,并使用Python QWeb实现。我们可以将此视为同一规范的两种实现,故我们需要了解其中的差异。

首先,使用Python语法对QWeb表达式进行计算,而不是使用JavaScript。对于最简单的表达式,可能很少或没有区别;但是若是更复杂的操作,表达式可能会有所不同。

表达式的计算方法也不同。对于报表,我们有以下变量:

• docs是一个可重复的集合,其中包含要打印的记录。

• doc_id是要打印的记录的ID列表。

• doc_model标识记录的模型,例如todo.task。

• time是对Python时间库的引用。

• user是运行报告的用户的记录。

• res_company是当前用户公司的记录。

报表内容是用HTML编写的,字段值可以使用t–field属性引用。可以用t–field–options属性来进行补充,效果是以特定的小部件来渲染字段内容。

现在我们可以开始为我们的报表设计页面内容了:

<!–– Report page content
<div class="row bg–primary">
<div class="col–xs–3">
<span class="glyphicon glyphicon–pushpin" /> What
</div>
<div class="col–xs–2">Who</div>
<div class="col–xs–l">When</div>
<div class="col–xs–3">Where</div>
<div class="col–xs–3">Watchers</div>
</div>
<t t–foreach="docs" t–as="o">
<div class="row">
<!-- Data Row Content -->
</div>
</t>

内容的布局可以使用Twitter Bootstrap HTML网格系统。简单地说,Bootstrap有一个包含12个列的网格布局。可以使用<div class="row">添加新的行。在一行中,我们有单元格,每一个都跨越一定数量的列,总计应当占据12列。每个单元格都可以用<div class="col–xs–N">来定义,其中N是它跨越的列数。

可以在http://getbootstrap.com上找到对Bootstrap的完整引用,描述上面介绍过的以及更多的样式元素。

在这里,我们添加标题行和标题,然后我们有一个t-foreach循环,遍历每个记录,并为每个记录渲染一行。

由于渲染是在服务器端完成的,所以记录是对象,我们可以使用点表示法从相关数据记录访问字段。这使得通过关系字段来访问它们的数据变得很容易。请注意,这在客户端渲染的Qweb、视图(如web客户端看板视图)中是不可能的。

以下XML可以渲染记录行的内容:

<div class="col–xs–3">
<h4><span t–field="o.name" /></h4>
</div>
<div class="col–xs–2">
<span t–field="o.user_id" />
</div>
<div class="col–xs–l">
<span t–field="o.date_deadline" />
<span t–field="o.amount_cost" t–field–options='(
"widget": "monetary", "display_currency": "o.currency_id"}'/>
</div>
<div class="col–xs–3">
<div t–field="res_company.partner_id" t–field–options='(
"widget": "contact",
"fields": ["address", "name", "phone", "fax"], "no_marker": true}' />
</div>
<div class="col–xs–3">
<!–– Render followers ––>
</div>

如我们所见,字段使用时可附带额外选项。这些非常类似于表单视图中使用的options属性。在第6章,视图-设计用户界面(Views – Designing the User Interface),使用附加widget来设置渲染字段的小部件。

一个例子是上面使用的货币小部件,紧挨着截止日期。

一个更复杂的示例是用于格式化地址的contact小部件。我们使用了公司地址res_company.partner_id,并且因为它有一些默认数据,我们可以立即看到渲染出的地址。但是,使用当前用户的地址,o.user_id.partner_id更有意义一些。默认情况下,contact小部件显示带有一些图标的地址,例如一个电话图标。我们使用的no_marker="true"选项禁用了它们。

渲染图片

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

我们报表的最后一部分将以带头像的关注者列表结束。我们将使用Bootstrap组件media–list,并循环关注者列表来渲染其中的每一个人:

<!–– Render followers ––>
<ul class="media–list">
<t t–foreach="o.message_follower_ids" t–as="f">
<li t–if="f.partner_id.image_small" class="media–left">
<img class="media–object" t–att–src="'data:image/png;base64,%s' %
f.partner_id.image_small" style="max–height: 24px;" />
<span class="media–body" t–field="f.partner_id.name" />
</li>
</t>
</ul>

二进制字段的内容以base64形式提供。<img>元素的src属性可以直接使用这类数据。因此,我们可以使用QWeb指令 t-att-src来动态生成每个图像。

汇总总数和累计总数

报表中经常需要提供总数。可以使用Python表达式来计算这些总数。

在<t t–foreach>标签结束之后,我们来添加用于计算总数的最后一行:

<!–– Totals ––>
<div class="row">
<div class="col–xs–3">
Count: <t t–esc="len(docs)" />
</div>
<div class="col–xs–2" />
<div class="col–xs–l"> Total:
<t t–esc="sum([o.amount_cost for o in docs])" />
</div>
<div class="col–xs–3" />
<div class="col–xs–3" />
</div>

len() Python语句用于计算集合中元素的数量。总数可以用sum()在一个值列表上计算。在前面的示例中,我们使用一个Python列表推导式从docs记录集生成一个值列表。您可以将列表推导式看作一个嵌入式的for循环。

有时我们想在报告中执行一些计算。例如,一个累计总数(循环到当前记录时的总和)。这可以用t-set来定义一个累加变量,然后在每一行上更新它。

为了说明这一点,我们可以试着计算关注者的累计数量。我们应该在循环docs记录集的t-foreach之前,初始化这个变量:

<t t–set="follower_count" t–value="0" />

然后,在循环中,将变量每次都将加上当前记录的关注者数量。我们选择在渲染出关注者列表后执行这个指令,并在每一行打印出当前的总数:

<!–– Running total––>
<t t–set="follower_count"
t–value="follower_count + len(o.message_follower_ids)" /> Accumulated # <t t–esc="follower_count" />

定义纸张格式

现在,我们的报表在HTML格式中看起来不错,但是它在PDF页面上并没有很好地打印出来。我们可以使用横向页面获得更好的结果。所以我们需要添加这种纸张格式。

在XML文件的顶部,添加以下记录:

<record id="paperformat_euro_landscape" model="report.paperformat">
<field name="name">European A4 Landscape</field>
<field name="default" eval="True" />
<field name="format">A4</field>
<field name="page_height">0</field>
<field name="page_width">0</field>
<field name="orientation">Landscape</field>
<field name="margin_top">40</field>
<field name="margin_bottom">23</field>
<field name="margin_left">7</field>
<field name="margin_right">7</field>
<field name="header_line" eval="False" />
<field name="header_spacing">35</field>
<field name="dpi">90</field>
</record>

它是一份欧洲A4格式的拷贝,源代码在addons/report/data, report_paperformat.xml文件中定义。但我们将纸张方向由纵向改到横向。从web客户端可以看到定义的文件格式,Settings Technical Reports Paper Format.。

现在我们可以在报表中使用它了。默认的文件格式是在公司设置中定义的,但是我们也可以指定特定报表使用的纸张格式。这是在报表操作中使用paperfomat属性完成的。

让我们编辑用于打开报告的动作,并添加此属性:

<report id="action_todo_task_report" string="To–do Tasks" model="todo.task" report_type="qweb–pdf"
name="todo_report.report_todo_task_template" paperformat="paperformat_euro_landscape"
/>

在9.0版本中添加了 <report>标签的paperformat属性。对于8.0,我们需要使用<record>元素来添加具有paperformat值的报表动作。

在报表中使用翻译

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

要为报表启用翻译,需要使用一个模板从模板调用它。这里使用的是带有t-lang属性的<t t–call>元素。

t-lang属性应该赋值为一个语言代码,比如es或en_US。它需要一个含有语言字段的记录。我们经常使用的是接收这张报表的合作伙伴的语言字段。客户的语言设置存储在partner_id.lang字段。在我们的例子中,我们没有合作伙伴字段,但是我们可以使用负责用户的语言,而相应的语言首选项是user_id.lang。

该函数需要传入一个模板名称,并将渲染和翻译它。这意味着我们需要在一个单独的模板中定义报告的页面内容,如下所示:

<report id="action_todo_task_report_translated" string="Translated To–do Tasks" model="todo.task"
report_type="qweb–pdf" name="todo_report.report_todo_task_translated" paperformat="paperformat_euro_landscape"
/>
<template id="report_todo_task_translated">
<t t–call="todo_report.report_todo_task_template" t–lang="user.lang" >
<t t–set="docs" t–value="docs" />
</t>
</t>
</template>

基于自定义SQL的报表

我们所建的报表是建立在常规记录的基础上的。但是在某些情况下,我们需要转换或聚合数据。在渲染报表时,处理动态数据并不是一件容易的事情。

其中一种方法是编写一个SQL查询来构建我们需要的数据集,然后通过一个特殊的模型存储这些结果,并根据获得记录集进行报表工作。

为此,我们将创建一个reports/todo_task_report.py文件,使用代码如下:

# –*– coding: utf–8 –*–
from odoo import models, fields
class TodoReport(models.Model):
_name = 'todo.task.report'
_description = 'To–do Report'
_sql = """
CREATE OR REPLACE VIEW todo_task_report AS SELECT *
FROM todo_task WHERE active = True """
name = fields.Char('Description') is_done = fields.Boolean('Done?') active = fields.Boolean('Active?')
user_id = fields.Many2one('res.users', 'Responsible') date_deadline = fields.Date('Deadline')

要加载这个文件,我们需要添加一个from . import reports行到 init .py文件的顶部,同时添加from . import todo_task_report到reports/ init .py文件。

sql属性用于覆盖数据库表的自动创建特性,同时提供一个SQL语句。我们希望它创建一个数据库视图来提供报告所需的数据。我们的SQL查询实际上非常简单,但关键是此时我们可以使用任何有效的SQL查询来生成我们的视图。

我们还映射了我们需要的ORM字段类型的字段,以便它们可以在这个模型上生成的记录集上使用。

接下来,我们可以根据这个模型添加一个新的报表,reports/todo_model_report.xml:

<odoo>
<report id="action_todo_model_report" string="To–do Special Report" model="todo.task" report_type="qweb–html"
name="todo_report.report_todo_task_special"
/>
<template id="report_todo_task_special">
<t t–call="report.html_container">
<t t–call="report.external_layout">
<div class="page">
<!–– Report page content ––>
<table class="table table–striped">
<tr>
<th>Title</th>
<th>Owner</th>
<th>Deadline</th>
</tr>
<t t–foreach="docs" t–as="o">
<tr>
<td class="col–xs–6">
<span t–field="o.name" />
</td>
<td class="col–xs–3">
<span t–field="o.user_id" />
</td>
<td class="col–xs–3">
<span t–field="o.date_deadline" />
</td>
</tr>
</t>
</table>
</div>
</t>
</t>
</template>
</odoo>

对于更复杂的情况,我们可以使用一个不同的解决方案:向导。为此,我们应该创建一个具有相关字段的暂态模型,其中头部包含了由用户引入的报告参数,并且这些字段会将查询出的数据提供给报告使用。这些行是由一个模型方法生成的,它可以包含我们可能需要的任何逻辑。强烈建议从现有的类似报告中获取灵感。

小结

不可思议!全球第一免费开源ERP Odoo如何开发BI报表

在前一章中,我们学习了QWeb,以及如何使用它来设计看板视图。在本章中,我们了解了QWeb报告引擎,以及使用QWeb模板语言构建报告时最重要的技术。

在下一章中,我们将继续使用QWeb,并构建网站页面。我们还将学习编写web控制器,为我们的web页面提供更丰富的特性。