Excel (XLSX) 导出样式

版本 16 中引入

get_xlsx_styles 模块方法允许你为报表导出定义自定义 Excel 样式。

注意: 此方法仅在将标准脚本报表或查询报表导出为 Excel 格式(非 CSV)时被调用。

基本用法

在报表的 Python 文件中定义一个 get_xlsx_styles 函数:

from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder

def get_xlsx_styles(metadata: XLSXMetadata) -> dict:
    builder = XLSXStyleBuilder(metadata)

    # Add your custom styles here...
    return builder.result

注意: 当 XLSXStyleBuilder 初始化时,默认样式(表头、筛选器、字段类型格式、货币)会自动应用。

要禁用此功能,请传入 default_styling=False:

builder = XLSXStyleBuilder(metadata, default_styling=False)

XLSXMetadata

metadata 对象提供对报表数据和辅助方法的访问。

属性

属性 类型 描述
report_name str 报表名称
filters dict 原始筛选值
column_map dict[int, dict] 列索引 → 列定义
row_map dict[int, dict \| list] 行索引 → 行数据
applied_filters_map dict[int, list] 筛选行索引 → [标签, 值]
has_total_row bool 最后一行是否为合计行
has_indentation bool 是否应用缩进样式

注意: 所有索引必须从 0 开始,遵循 XlsxWriter 的索引规则。

XLSXStyleBuilder

该构建器提供注册和应用样式的方法。

注册样式

# Register a style and get its ID
style_id = builder.register_style({"bold": True, "bg_color": "#FFFF00"})

应用样式

# Style an entire column
builder.style_column(col_idx, style_id)

# Style an entire row
builder.style_row(row_idx, style_id)

# Style a specific cell
builder.style_cell(row_idx, col_idx, style_id)

样式属性

字体样式

builder.register_style({
    "bold": True,
    "italic": True,
    "underline": True,
    "font_size": 12,
    "font_color": "#FF0000",
    "font_name": "Arial",
})

背景颜色

builder.register_style({
    "bg_color": "#FFFF00",
})

边框

# Border thickness: 1 = thin, 2 = medium, 3 = thick
builder.register_style({
    "border": 1,
    "border_color": "#000000",
})

# Individual borders
builder.register_style({
    "left": 2,
    "right": 2,
    "top": 1,
    "bottom": 3,
})

对齐方式

builder.register_style({
    "align": "center",           # left, center, right
    "valign": "vcenter",         # top, vcenter, bottom
    "text_wrap": True,
    "indent": 2,

})

数字格式

builder.register_style({
    "num_format": "#,##0.00",    # Custom number format

})

完整示例

from frappe.utils.xlsxutils import XLSXMetadata, XLSXStyleBuilder

def get_xlsx_styles(metadata: XLSXMetadata) -> dict:
    builder = XLSXStyleBuilder(metadata)

    # --- Register Styles (do this once, reuse style_id) --- #
    success_style = builder.register_style({"font_color": "#006100", "bg_color": "#C6EFCE"})
    warning_style = builder.register_style({"font_color": "#9C5700", "bg_color": "#FFEB9C"})
    error_style = builder.register_style({"font_color": "#9C0006", "bg_color": "#FFC7CE"})
    highlight_style = builder.register_style({"bold": True, "bg_color": "#DDEBF7"})
    total_row_border = builder.register_style({"border": 3, "border_color": "#15137e"})
    right_align = builder.register_style({"align": "right"})

    # --- Style entire column --- #
    amount_col_idx = builder.field_index_map.get("net_amount")

    if amount_col_idx is not None:
        builder.style_column(amount_col_idx, right_align)

    # --- Style total row with border --- #
    if metadata.has_total_row:
        builder.style_row(metadata.get_last_row_index(), total_row_border)

    # --- Conditional Cell Styling --- #
    status_col_idx = builder.field_index_map.get("status")

    for row_idx, row_data in metadata.row_map.items():
        if not isinstance(row_data, dict):
            continue

        # Status-based styling
        if status_col_idx is not None:
            status = row_data.get("status")

            if status == "Completed":
                builder.style_cell(row_idx, status_col_idx, success_style)

            elif status == "Pending":
                builder.style_cell(row_idx, status_col_idx, warning_style)

            elif status == "Cancelled":
                builder.style_cell(row_idx, status_col_idx, error_style)

        # High value highlighting
        if amount_col_idx is not None:
            amount = row_data.get("net_amount")

            if amount and amount > 10000:
                builder.style_cell(row_idx, amount_col_idx, highlight_style)

    return builder.result

示例输出


带样式的导出示例

建议

  • 注册一次样式并重复使用 — 在开始时注册样式,并在整个过程中使用返回的 style_id,以避免创建重复样式。

参考

  • 有关所有可用的样式属性,请访问 xlsxwriter 格式文档。

报表生成器

报表生成器是最简单的报表类型,无需编写任何代码即可创建。
它只能显示单个 DocType 的记录以及子表的子记录。

保存报表

默认情况下,所有 DocType 都会生成一个报表视图。用户还可以根据不同的筛选条件、排序方式、列等组合保存多个报表。


报表生成器

更新或删除自定义报表

用户使用报表生成器创建的任何自定义报表,都可以在报表视图中进行更新或删除。


更新或删除自定义报表

打印

报表可以通过菜单进行打印。


报表生成器打印

分组

你还可以对列应用分组条件,并使用计数、求和、平均值等聚合函数。


报表生成器分组

键盘导航与编辑

由于报表生成器是单个 DocType 的视图,因此也可以进行编辑。


报表生成器编辑

查询报表

查询报表是可以通过单条 SQL 查询生成的报表。
只要查询能生成列和记录,查询可以简单也可以复杂。
这些报表只能由系统管理员创建,并存储在数据库中。

要创建查询报表,请在快捷搜索栏中输入“新建报表”并按回车。

  1. 将报表类型设置为“查询报表”
  2. 设置参考文档类型 – 有权访问该参考文档类型的用户将有权访问该报表
  3. 设置模块 – 报表将显示在该模块的“自定义报表”部分。
  4. 编写你的查询

如果你将“标准”设置为“是”并且启用了开发者模式,那么将生成一个 JSON 文件,
你需要将其签入到你的版本控制中。只有当你想要将查询报表与你的应用一起打包时,
才应该这样做。模块将决定 JSON 文件存放的位置。

列和筛选器

在版本 13 中添加

你可以在报表文档中配置列和筛选器。在这里,你可以为列和筛选器设置标签、宽度、格式(字段类型)。

筛选器可以在查询中用作格式化变量。例如,类型为 customer 的筛选器可以在查询中用作 %(customer)s。

示例

SELECT
 name, creation, production_item, qty, produced_qty, company
FROM
 `tabWork Order`
WHERE
 docstatus=1
 AND ifnull(produced_qty,0) < qty

格式化列(旧式)

或者,也可以通过以特定格式指定列的标签来格式化列:{label}:{optional fieldtype}{optional /}{optional options}:{optional width}

如果你已经在报表本身中配置了字段和列,则无需使用这种样式。

示例(旧式)

下面是一个查询可能的样子:

SELECT
 `tabWork Order`.name as "Work Order:Link/Work Order:200",
 `tabWork Order`.creation as "Date:Date:120",
 `tabWork Order`.production_item as "Item:Link/Item:150",
 `tabWork Order`.qty as "To Produce:Int:100",
 `tabWork Order`.produced_qty as "Produced:Int:100",
 `tabWork Order`.company as "Company:Link/Company:"
FROM
 `tabWork Order`
WHERE
 `tabWork Order`.docstatus=1
 AND ifnull(`tabWork Order`.produced_qty,0) < `tabWork Order`.qty
 AND NOT EXISTS (SELECT name from `tabStock Entry` where work_order =`tabWork Order`.name)

如果你注意到每一列都有一种特殊语法,我们会使用这些信息来格式化报表视图。

例如:第一列 Work Order:Link/Work Order:200 将渲染为文档类型为“工单”的链接字段,列宽将为 200px。


查询报表视图

脚本报表

任何无法通过报表构建器或查询报表实现的功能,都可以通过脚本报表来实现。顾名思义,这些报表是用 Python 脚本构建的。由于这些报表通过 Python 脚本为您提供了不受限制的访问权限,因此只能由管理员创建。这些报表是在开发过程中编写的,并且是您应用的一部分。

要创建脚本报表,您必须启用开发者模式。

要创建脚本报表,请在快捷搜索栏中输入”new report”并按回车。

  1. 将报表类型设置为”脚本报表”
  2. 将”是否标准”设置为”是”
  3. 选择您要添加此报表的模块
  4. 在模块文件夹中(例如,如果是 ERPNext 中的 Accounts,文件夹
    将是 erpnext/accounts/report/[report-name]),您会看到模板
    或报表文件将被创建。
  5. 在生成的 {report-name}.py 文件中编写您的 Python 脚本。
  6. 您可以通过将筛选器添加到 {report-name}.js 来为报表添加筛选器。


脚本报表

标准报表和自定义报表

在版本 12 中添加

从版本 12 开始,您可以在 Frappe 框架中创建自定义查询报表和脚本报表。在自定义报表中,脚本可以直接添加到报表本身中,您可以使用 Frappe 框架的脚本 API 函数。

列和筛选器

在版本 13 中添加

您可以在报表文档中配置列和筛选器。在这里,您可以为列和筛选器设置标签、宽度、格式(字段类型)。

筛选器可以在查询中用作格式化变量。例如,类型为 customer 的筛选器可以在查询中用作 %(customer)s。

编写脚本

自定义报表

在自定义报表中,您可以使用脚本 API 并直接在代码部分编写脚本。

return frappe.db.get_all('User', ['first_name', 'last_name'], filters = filters)

标准报表

生成的 .py 文件附带了一个报表的样板代码。其中有一个名为 execute 的方法,它接收 filters 并返回 columns 和 data。您可以使用任何 Python 模块和 SQL 查询的组合来生成报表。execute 函数如下所示

from __future__ import unicode_literals
# import frappe

def execute(filters=None):
 columns, data = [], []
 return columns, data

execute 函数应返回默认在报表中显示的 columns 和 data。开发者可以选择性地返回一些参数,如 message、chart、report_summary、skip_total_rows。

以下是 execute 函数可以返回的参数

列

这是一个字典列表。它按顺序保存了要在数据表中显示的所有列。

注意:只有在报表中未指定列时,您才需要返回列

示例:

columns = [
 {
 'fieldname': 'account',
 'label': _('Account'),
 'fieldtype': 'Link',
 'options': 'Account'
 },
 {
 'fieldname': 'currency',
 'label': _('Currency'),
 'fieldtype': 'Link',
 'options': 'Currency'
 },
 {
 'fieldname': 'balance',
 'label': _('Balance'),
 'fieldtype': 'Currency',
 'options': 'currency'
 }
 ]

结果

这可以是一个列表的列表或字典的列表。它保存了要在报表中显示的数据

示例:

data = [
 {
 'account': 'Application of Funds (Assets)',
 'currency': 'INR',
 'balance': '15182212.738'
 },
 {
 'account': 'Current Assets - GTPL',
 'currency': 'INR',
 'balance': '17117932.738'
 },
 ...
 ]

图表

包含报表中默认显示的图表的配置。

报表摘要

这是一个字典列表,用于存储报表中的重要数值,并在界面顶部区域单独显示。

示例:

[{
 "value": profit,
 "indicator": "Green" if profit > 0 else "Red",
 "label": _("Total Profit This Year"),
 "datatype": "Currency",
 "currency": "INR"
}]

注意:这些参数需要按照以下特定顺序返回

以下是来自 ERPNext 的一个脚本报表:资产负债表

添加筛选器

要在报表中添加筛选器,请在
{report-name}.js 文件中定义字段及其字段类型。筛选器的值将在 execute
方法中以字典形式提供。

frappe.query_reports['Balance Sheet'] = {
 filters: [
 {
 fieldname: 'company',
 label: __('Company'),
 fieldtype: 'Link',
 options: 'Company',
 default: frappe.defaults.get_user_default('company')
 },
 {
 fieldname: 'periodicity',
 label: __('Periodicity'),
 fieldtype: 'Select',
 options: [
 'Monthly',
 'Quarterly',
 'Half-Yearly',
 'Yearly'
 ],
 default: 'Yearly',
 depends_on: 'eval:doc.company=="Gadget Technologies Pvt. Ltd."'
 }
 ]
}

类似于控制字段显示的 depends_on 属性,在
版本 13 中我们为脚本报表筛选器引入了 depends_on。它可以
用于根据 depends_on 中条件的值来确定筛选器是否可见。


资产负债表


资产负债表

专业提示:要直接导航到上述任意类型的报表,请在快捷搜索栏中输入其
名称并按回车。

快照报表

一种高级报表生成引擎。它需要手动重写报表以使用 OLAP 数据库。

  1. 在系统设置 -> 高级中启用”快照报表”。这会设置一个后台任务,将数据从 MariaDB 同步到 DuckDB
  2. 在报表主数据中勾选”快照报表”复选框
  3. 在报表模块中使用 execute_snapshot_report 方法重写报表。
def execute_snapshot_report(filters: dict | None = None):
    """Return columns and data for the report.

    This is the main entry point for snapshot report. When 'Synced
    Report' is enabled in report, framework will call this method 
    every time the report is refreshed or a filter is updated. It 
    accepts the same filters as normal execute. But a utility method - 
    get_latest_sync, is also imported.
    """

    from frappe.database.duckdb.database import get_latest_sync
    columns = get_columns()
    data = get_data()

    return columns, data