mcpField
R
reports

reports

github.com/lubusinデータ・データベース68116 回インストール13 週間前に更新
リポジトリ

reports について

Frappe 開発向けのレポート作成スキルです。ノーコードの Report Builder から SQL、Python、JavaScript による実装まで扱い、フィルター、書式、権限、印刷レイアウトも説明します。

主な機能

  1. 01Report Builder の設定
  2. 02SQL クエリレポートの作成
  3. 03Python・JavaScript スクリプトレポート
  4. 04フィルター・書式・チャートの設定
  5. 05カスタム印刷レイアウトの作成
  6. 06権限とクエリの安全対策

活用シーン

  1. 01コードなしでグループ別レポートを作成
  2. 02SQL の結合や集計を使ったレポート作成
  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 を明示的にフィルターします

コメント