mcpField
F
frappe-impl-reports

frappe-impl-reports

github.com/impertio-studioデータ・データベース198110 回インストール30 週間前に更新
リポジトリ

frappe-impl-reports について

Frappe 開発者向けのレポート構築ガイドです。Query Report、Script Report、Report Builder、Prepared Report の使い分けに加え、チャート、数値カード、ダッシュボード、クエリの最適化を扱います。

主な機能

  1. 01用途に合ったレポート種別の選択
  2. 02SQL またはスクリプトレポートの作成
  3. 03レポートへのチャートとサマリーの追加
  4. 04Prepared Report の設定
  5. 05数値カードとダッシュボードの構成、クエリ最適化

活用シーン

  1. 01SQL で業務レコードを集計
  2. 02複雑なレポートにチャートと主要指標を追加
  3. 03大規模データのレポートをバックグラウンド生成
  4. 04チャートと数値カードをダッシュボードに配置

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/impertio-studio/frappe_claude_skill_package --skill frappe-impl-reports

インストール後、Claude Code で/frappe-impl-reportsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
.gitkeep
anti-patterns.md
examples.md
workflows.md

詳細

Frappe レポートの作成

クイックリファレンス

レポートの種類最適な用途アクセス権ファイル
クエリレポートシンプルな SQL クエリSystem Manager のみDocType 内の SQL または .py
スクリプトレポート複雑なロジック、グラフAdministrator + Dev Mode.py + .js
レポートビルダーエンドユーザー向けのアドホックレポート権限のあるすべてのユーザーUI のみ
準備済みレポート大規模なデータセット(>100k 行)元のレポートと同じバックグラウンドジョブ

判断ツリー:どのレポートの種類を選ぶか?

Need a report?
├─ End user builds it themselves? → Report Builder
├─ Simple SQL with no Python logic? → Query Report
├─ Complex logic / charts / summary? → Script Report
│   └─ Dataset > 100k rows or timeout? → Add prepared_report = True
└─ Real-time KPI on workspace? → Number Card or Dashboard Chart

1. スクリプトレポートの作成

ファイル構成
my_app/my_module/report/sales_summary/
├── sales_summary.json    # Report DocType definition
├── sales_summary.py      # Python: execute() function
└── sales_summary.js      # JavaScript: filters + config

必ず Desk から作成してください:Report > New > Script Report > Developer Mode で「Is Standard = Yes」に設定します。

Python:execute() 関数
# sales_summary.py
import frappe
from frappe import _

def execute(filters=None):
    columns = get_columns()
    data = get_data(filters)
    chart = get_chart(data)
    report_summary = get_summary(data)
    return columns, data, None, chart, report_summary

def get_columns():
    return [
        {"fieldname": "customer", "label": _("Customer"), "fieldtype": "Link",
         "options": "Customer", "width": 200},
        {"fieldname": "total", "label": _("Total"), "fieldtype": "Currency",
         "options": "currency", "width": 120},
        {"fieldname": "qty", "label": _("Qty"), "fieldtype": "Int", "width": 80},
        {"fieldname": "posting_date", "label": _("Date"), "fieldtype": "Date", "width": 100},
    ]

def get_data(filters):
    conditions = get_conditions(filters)
    return frappe.db.sql("""
        SELECT
            si.customer, SUM(si.grand_total) as total,
            SUM(si.total_qty) as qty, si.posting_date
        FROM `tabSales Invoice` si
        WHERE si.docstatus = 1 {conditions}
        GROUP BY si.customer
        ORDER BY total DESC
    """.format(conditions=conditions), filters, as_dict=True)

def get_conditions(filters):
    conditions = ""
    if filters.get("from_date"):
        conditions += " AND si.posting_date >= %(from_date)s"
    if filters.get("to_date"):
        conditions += " AND si.posting_date <= %(to_date)s"
    if filters.get("company"):
        conditions += " AND si.company = %(company)s"
    return conditions

戻り値の順序(位置指定 — 必ずこの順序を維持してください):

位置名前型必須
1columnslist[dict]はい
2datalist[dict] または list[list]はい
3messagestr または Noneいいえ
4chartdict または Noneいいえ
5report_summarylist[dict] または Noneいいえ
6skip_total_rowsboolいいえ
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),
            reqd: 1
        },
        {
            fieldname: "to_date",
            label: __("To Date"),
            fieldtype: "Date",
            default: frappe.datetime.get_today(),
            reqd: 1
        },
        {
            fieldname: "customer_group",
            label: __("Customer Group"),
            fieldtype: "Link",
            options: "Customer Group",
            depends_on: "eval:doc.company"
        }
    ],
    formatter: function(value, row, column, data, default_formatter) {
        value = default_formatter(value, row, column, data);
        if (column.fieldname === "total" && data.total > 100000) {
            value = "<span style='color:green;font-weight:bold'>" + value + "</span>";
        }
        return value;
    }
};

2. クエリレポートの作成

クエリレポートでは生の SQL を使用します。SQL エイリアスには必ず従来の列形式を使用してください:

SELECT
    `tabWork Order`.name AS "Work Order:Link/Work Order:200",
    `tabWork Order`.creation AS "Date:Date:120",
    `tabWork Order`.company AS "Company:Link/Company:150",
    `tabWork Order`.qty AS "Qty:Int:80",
    `tabWork Order`.grand_total AS "Total:Currency:120"
FROM `tabWork Order`
WHERE `tabWork Order`.docstatus = 1
ORDER BY `tabWork Order`.creation DESC

列形式:「Label:Fieldtype/Options:Width」

WHERE 句のフィルター変数には %(filter_name)s を使用します。

3. レポートへのグラフの追加

execute() の4番目の要素として、グラフの dict を返します:

def get_chart(data):
    labels = [d.customer for d in data[:10]]
    values = [d.total for d in data[:10]]
    return {
        "data": {
            "labels": labels,
            "datasets": [{"name": _("Revenue"), "values": values}]
        },
        "type": "bar",            # bar | line | pie | donut | percentage
        "colors": ["#7cd6fd"],
        "barOptions": {"stacked": False},  # for bar charts
        "height": 300
    }

グラフの種類:bar、line、pie、donut、percentage。

複数データセットのグラフ(期間の比較など)では:

"datasets": [
    {"name": "2024", "values": [10, 20, 30]},
    {"name": "2025", "values": [15, 25, 35]}
]

4. レポートサマリーの追加

5番目の要素として、サマリー dict のリストを返します:

def get_summary(data):
    total_revenue = sum(d.total for d in data)
    total_qty = sum(d.qty for d in data)
    return [
        {"value": total_revenue, "label": _("Total Revenue"),
         "datatype": "Currency", "currency": "USD",
         "indicator": "Green" if total_revenue > 0 else "Red"},
        {"value": total_qty, "label": _("Total Qty"),
         "datatype": "Int", "indicator": "Blue"},
        {"value": len(data), "label": _("Customers"),
         "datatype": "Int", "indicator": "Grey"}
    ]

インジケーターの色:Green、Blue、Orange、Red、Grey。

5. 準備済みレポート

大規模なデータセットでタイムアウトするレポートでは、.js ファイルに追加します:

frappe.query_reports["Heavy Report"] = {
    filters: [ /* ... */ ],
    prepared_report: true    // enables background generation
};

prepared_report: true を設定すると、Frappe はバックグラウンドジョブを通じてレポートをキューに登録します。ユーザーにはキャッシュされた結果が表示され、必要に応じて再生成できます。

6. Number Card

ワークスペースのダッシュボード向け Number Card には3つの種類があります:

種類ソース用途
Document TypeDocType の集計ドキュメントの件数/合計
ReportScript/Query Reportレポートデータからの KPI
Customホワイトリスト登録されたメソッド任意の計算値
Document Type Number Card

Desk > Number Card から作成します。DocType、集計関数(Count/Sum/Avg)、フィルターを設定します。

レポートベースの Number Card

既存のレポートを指定します。カードには最初の行の最初の数値列が表示されます。

カスタムメソッド Number Card
# In your app, create a whitelisted method:
@frappe.whitelist()
def get_open_tickets():
    count = frappe.db.count("Issue", {"status": "Open"})
    return {"value": count, "fieldtype": "Int", "route_options": {"status": "Open"},
            "route": ["query-report", "Open Issues"]}

7. ダッシュボードチャート

Desk > Dashboard Chart から作成するか、フィクスチャ内でプログラムから作成します:

# hooks.py
fixtures = [
    {"dt": "Dashboard Chart", "filters": [["module", "=", "My Module"]]}
]

ソースの種類:Report、Group By、Custom(ホワイトリスト登録されたメソッド)。

Group By チャート
{
    "chart_name": "Invoices by Status",
    "chart_type": "Group By",
    "document_type": "Sales Invoice",
    "group_by_type": "Count",
    "group_by_based_on": "status",
    "type": "Donut",
    "filters_json": "{\"docstatus\": 1}"
}

8. ダッシュボードの作成

ダッシュボードには複数のチャートと Number Card を組み合わせます:

{
    "name": "Sales Dashboard",
    "module": "Selling",
    "charts": [
        {"chart": "Monthly Revenue", "width": "Full"},
        {"chart": "Invoices by Status", "width": "Half"},
        {"chart": "Top Customers", "width": "Half"}
    ],
    "cards": [
        {"card": "Total Revenue"},
        {"card": "Open Orders"}
    ]
}

9. パフォーマンスの最適化

  • WHERE/GROUP BY で使用する列には必ずインデックスを追加してください(frappe.model.utils.add_index)
  • frappe.db.sql() では必ず as_dict=True を使用してください — 列のフィールド名に一致します
  • SELECT * は使用しないでください — 必要な列を正確に指定してください
  • レポートのループ内でドキュメント全体を読み込まないでください(frappe.get_doc)— SQL を使用してください
  • v14 以降では、パラメーター化クエリに frappe.qb(クエリビルダー)を使用してください
  • 50k 行を超えるレポートでは、必ず prepared_report: true を有効にしてください
  • 下書き/キャンセル済みのドキュメントを除外するため、必ず docstatus でフィルターしてください

10. よく使われるパターン

日付範囲フィルターのパターン
if filters.get("from_date") and filters.get("to_date"):
    conditions += " AND posting_date BETWEEN %(from_date)s AND %(to_date)s"
複数通貨のパターン
{"fieldname": "amount", "label": _("Amount"), "fieldtype": "Currency",
 "options": "currency", "width": 120}
# "options": "currency" means use the row's "currency" field for formatting
合計付きグループ化のパターン
data = frappe.db.sql("""
    SELECT customer, COUNT(*) as count, SUM(grand_total) as total
    FROM `tabSales Invoice`
    WHERE docstatus = 1 {conditions}
    GROUP BY customer WITH ROLLUP
""".format(conditions=conditions), filters, as_dict=True)

関連項目

コメント