mcpField
F
frappe-reports

frappe-reports

github.com/lubusin数据与数据库69211 次安装12 周前更新
代码仓库

关于 frappe-reports

frappe-reports 是面向 Frappe 开发者的报表构建技能,说明如何选择 Report Builder、SQL Query Report 或 Python 与 JavaScript Script Report,并设置筛选器、图表和打印格式。

主要功能

  1. 01配置 Report Builder
  2. 02SQL Query Report
  3. 03Python 和 JavaScript Script Report
  4. 04筛选器、格式与图表
  5. 05自定义报表打印格式

适用场景

  1. 01创建 DocType 分组汇总
  2. 02编写参数化 SQL 报表
  3. 03构建含计算字段的报表
  4. 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

通过界面创建,无需编写代码:

  1. 导航到 Report 列表 → New Report
  2. 选择 Reference DocType
  3. 选择 Report Type = "Report Builder"
  4. 在构建器界面中添加列、筛选条件、排序和分组
2) Query Report

使用原始 SQL 查询的报表:

  1. 创建 Report → Type = "Query Report"
  2. 设置 Reference DocType(用于控制权限)
  3. 编写 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

参考资料

使用防护措施

  • 验证筛选条件:构建查询前检查筛选值;处理空值或无效输入
  • 处理空结果:始终处理查询无数据返回的情况;显示适当的提示
  • 使用 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