mcpField
R
reports

reports

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

关于 reports

面向 Frappe 开发的报表技能,涵盖从无代码 Report Builder 到 SQL、Python 和 JavaScript 报表的实现。内容还涉及筛选器、格式、图表、打印布局、权限与查询安全。

主要功能

  1. 01配置 Report Builder
  2. 02编写 SQL 查询报表
  3. 03开发 Python 和 JavaScript 脚本报表
  4. 04设置筛选器、格式和图表
  5. 05制作自定义打印布局
  6. 06落实权限与查询安全

适用场景

  1. 01无代码创建分组报表
  2. 02用 SQL 联接和汇总数据
  3. 03实现计算字段和动态筛选器
  4. 04制作自定义打印版式

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/lubusin/frappe-skills --skill reports

安装后在 Claude Code 中输入/reports确认已加载。

附带文件references/reports.md

详细介绍

Frappe 报表

使用 Report Builder、Query Reports(SQL)或 Script Reports(Python + JS)构建报表。

适用场景

  • 创建数据分析或汇总报表
  • 构建基于 SQL 的查询报表
  • 使用 Python 逻辑和 JS UI 实现复杂报表
  • 为报表添加自定义筛选器、格式化程序和图表
  • 创建可打印的报表格式

所需输入

  • 报表用途和数据要求
  • 报表的数据源 DocType
  • 筛选器要求
  • 列定义(字段、类型、格式)
  • 报表是标准报表(随应用打包)还是自定义报表(针对特定站点)

流程

0) 选择报表类型
类型复杂度是否需要代码适用场景
Report Builder低无简单的字段选择、分组和排序
Query Report中仅 SQL直接 SQL 查询、联接和聚合
Script Report高Python + JS复杂逻辑、计算字段和动态筛选器
1) Report Builder

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

  1. 前往 Report 列表 → New Report
  2. 选择 Reference DocType
  3. 选择 Report Type = "Report Builder"
  4. 通过构建器 UI 添加列、筛选器、排序和分组
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 架构 → doctype-development
  • 关于 API 端点(通过 API 获取报表数据)→ api-development
  • 关于 Desk UI 自定义 → 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