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. 01Report Builder の設定
  2. 02SQL Query Report
  3. 03Python と JavaScript の Script Report
  4. 04フィルター、書式、チャート
  5. 05カスタム印刷形式

活用シーン

  1. 01DocType のグループ集計作成
  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 レポート

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. Create 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 UI のカスタマイズについて → 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 を明示的にフィルターします

コメント