关于 frappe-reports
frappe-reports 是面向 Frappe 开发者的报表构建技能,说明如何选择 Report Builder、SQL Query Report 或 Python 与 JavaScript Script Report,并设置筛选器、图表和打印格式。
主要功能
- 01配置 Report Builder
- 02SQL Query Report
- 03Python 和 JavaScript Script Report
- 04筛选器、格式与图表
- 05自定义报表打印格式
适用场景
- 01创建 DocType 分组汇总
- 02编写参数化 SQL 报表
- 03构建含计算字段的报表
- 04添加筛选器和可打印版式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ lubusin/ frappe-skills --skill frappe-reports安装后在 Claude Code 中输入/frappe-reports确认已加载。
附带文件references/reports.md
详细介绍
Frappe Reports
使用 Report Builder、Query Reports(SQL)或 Script Reports(Python + JS)构建报表。
适用场景
- 创建数据分析或汇总报表
- 构建基于 SQL 的查询报表
- 使用 Python 逻辑和 JS 界面实现复杂报表
- 为报表添加自定义筛选条件、格式化器和图表
- 创建可打印的报表格式
所需输入
- 报表用途和数据需求
- 报表的来源 DocType
- 筛选条件需求
- 列定义(字段、类型、格式)
- 报表是标准报表(随应用打包)还是自定义报表(特定站点)
流程
0) 选择报表类型
| 类型 | 复杂度 | 是否需要代码 | 最适合用于 |
|---|---|---|---|
| Report Builder | 低 | 无 | 简单的字段选择、分组和排序 |
| Query Report | 中 | 仅 SQL | 直接编写 SQL 查询、连接和聚合 |
| Script Report | 高 | Python + JS | 复杂逻辑、计算字段、动态筛选条件 |
1) Report Builder
通过界面创建,无需编写代码:
- 导航到 Report 列表 → New Report
- 选择 Reference DocType
- 选择 Report Type = "Report Builder"
- 在构建器界面中添加列、筛选条件、排序和分组
2) Query Report
使用原始 SQL 查询的报表:
- 创建 Report → Type = "Query Report"
- 设置 Reference DocType(用于控制权限)
- 编写 SQL 查询
SELECT
`tabSales Order`.name AS "Sales Order:Link/Sales Order:200",
`tabSales Order`.customer AS "Customer:Link/Customer:200",
`tabSales Order`.transaction_date AS "Date:Date:120",
`tabSales Order`.grand_total AS "Grand Total:Currency:150",
`tabSales Order`.status AS "Status:Data:100"
FROM `tabSales Order`
WHERE `tabSales Order`.docstatus = 1
{% if filters.company %}
AND `tabSales Order`.company = %(company)s
{% endif %}
{% if filters.from_date %}
AND `tabSales Order`.transaction_date >= %(from_date)s
{% endif %}
ORDER BY `tabSales Order`.transaction_date DESC
SELECT 中的列格式:"Label:Fieldtype/Options:Width"
| Fieldtype | 示例 |
|---|---|
| Link | "Customer:Link/Customer:200" |
| Currency | "Amount:Currency:150" |
| Date | "Date:Date:120" |
| Int | "Quantity:Int:100" |
| Data | "Status:Data:100" |
筛选变量:参数化查询中使用 %(filter_name)s。
3) Script Report(标准报表)
适用于随应用打包、需要完全控制 Python + JS 的报表:
创建报表结构:
my_app/
└── my_module/
└── report/
└── sales_summary/
├── sales_summary.json # Report metadata
├── sales_summary.py # Python data logic
└── sales_summary.js # JS filters and UI
Python 脚本(sales_summary.py):
import frappe
from frappe import _
def execute(filters=None):
columns = get_columns()
data = get_data(filters)
chart = get_chart(data)
return columns, data, None, chart
def get_columns():
return [
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"width": 200
},
{
"label": _("Total Orders"),
"fieldname": "total_orders",
"fieldtype": "Int",
"width": 120
},
{
"label": _("Total Amount"),
"fieldname": "total_amount",
"fieldtype": "Currency",
"width": 150
},
{
"label": _("Average Order"),
"fieldname": "avg_order",
"fieldtype": "Currency",
"width": 150
}
]
def get_data(filters):
conditions = get_conditions(filters)
data = frappe.db.sql("""
SELECT
customer,
COUNT(name) as total_orders,
SUM(grand_total) as total_amount,
AVG(grand_total) as avg_order
FROM `tabSales Order`
WHERE docstatus = 1 {conditions}
GROUP BY customer
ORDER BY total_amount DESC
""".format(conditions=conditions), filters, as_dict=True)
return data
def get_conditions(filters):
conditions = ""
if filters.get("company"):
conditions += " AND company = %(company)s"
if filters.get("from_date"):
conditions += " AND transaction_date >= %(from_date)s"
if filters.get("to_date"):
conditions += " AND transaction_date <= %(to_date)s"
return conditions
def get_chart(data):
if not data:
return None
return {
"data": {
"labels": [d.customer for d in data[:10]],
"datasets": [{
"name": _("Total Amount"),
"values": [d.total_amount for d in data[:10]]
}]
},
"type": "bar"
}
JavaScript 脚本(sales_summary.js):
frappe.query_reports["Sales Summary"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1)
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today()
}
],
onload(report) {
// Custom initialization
},
formatter(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
// Highlight high-value customers
if (column.fieldname === "total_amount" && data.total_amount > 100000) {
value = `<span style="color: green; font-weight: bold">${value}</span>`;
}
return value;
}
};
报表 JSON(sales_summary.json):
{
"name": "Sales Summary",
"doctype": "Report",
"report_type": "Script Report",
"ref_doctype": "Sales Order",
"module": "My Module",
"is_standard": "Yes",
"disabled": 0
}
4) 添加报表打印格式
在报表文件夹中创建 sales_summary.html,用于自定义打印布局:
<h2>Sales Summary Report</h2>
<table class="table table-bordered">
<tr>
<th>Customer</th>
<th>Orders</th>
<th>Total</th>
</tr>
{% for row in data %}
<tr>
<td>{{ row.customer }}</td>
<td>{{ row.total_orders }}</td>
<td>{{ frappe.format(row.total_amount, {fieldtype: 'Currency'}) }}</td>
</tr>
{% endfor %}
</table>
5) 在 hooks 中注册报表(可选)
只要符合标准目录结构,系统就会自动发现报表。标准报表无需在 hooks.py 中添加条目。
验证
- 报表显示在 Report 列表中
- 筛选条件正常工作并影响结果
- 列以正确的格式显示
- 图表正常渲染(如适用)
- 权限得到遵守(只有授权用户可以查看数据)
- 打印格式正常工作
- 性能满足预期数据量的要求
故障情况 / 调试
- 找不到报表:检查模块路径和
is_standard设置;运行bench migrate - SQL 语法错误:先在
bench --site <site> mariadb中测试查询 - 未返回数据:检查
docstatus筛选条件;确认筛选条件与数据匹配 - 权限被拒绝:检查用户角色对 Reference DocType 的权限
- 查询缓慢:添加索引;使用 Query Builder;限制结果集
升级处理
- DocType 架构相关问题 →
frappe-doctype-development - API 端点(通过 API 获取报表数据)→
frappe-api-development - Desk 界面自定义 →
frappe-desk-customization
参考资料
- references/reports.md — 报表类型、创建方法和示例
使用防护措施
- 验证筛选条件:构建查询前检查筛选值;处理空值或无效输入
- 处理空结果:始终处理查询无数据返回的情况;显示适当的提示
- 使用
frappe.db.escape():转义 SQL 查询中的用户输入,防止注入 - 限制结果集:为大型数据集添加 LIMIT 子句或分页
- 在 execute 中检查权限:确认用户有权查看这些数据
常见错误
| 错误 | 失败原因 | 修复方法 |
|---|---|---|
| 通过筛选条件进行 SQL 注入 | 安全漏洞 | 使用 frappe.db.escape() 或带参数的 Query Builder |
| 缺少权限检查 | 未授权的数据访问 | 验证 frappe.has_permission(),或按允许访问的记录进行筛选 |
| 查询范围不受限制 | 超时、内存问题 | 添加 LIMIT、使用分页,或按日期范围筛选 |
| 列的字段类型错误 | 格式问题 | 让列的 fieldtype 与数据匹配(Currency、Date 等) |
| 未处理聚合中的 None | 错误或总计有误 | 在 SQL 中使用 COALESCE() 或 IFNULL() |
假定 docstatus 固定不变 | 漏掉草稿或已取消的记录 | 根据报表需求显式筛选 docstatus |
评论
支持 Markdown