frappe-impl-reports について
Frappe 開発者向けのレポート構築ガイドです。Query Report、Script Report、Report Builder、Prepared Report の使い分けに加え、チャート、数値カード、ダッシュボード、クエリの最適化を扱います。
主な機能
- 01用途に合ったレポート種別の選択
- 02SQL またはスクリプトレポートの作成
- 03レポートへのチャートとサマリーの追加
- 04Prepared Report の設定
- 05数値カードとダッシュボードの構成、クエリ最適化
活用シーン
- 01SQL で業務レコードを集計
- 02複雑なレポートにチャートと主要指標を追加
- 03大規模データのレポートをバックグラウンド生成
- 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/.gitkeepanti-patterns.mdexamples.mdworkflows.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
戻り値の順序(位置指定 — 必ずこの順序を維持してください):
| 位置 | 名前 | 型 | 必須 |
|---|---|---|---|
| 1 | columns | list[dict] | はい |
| 2 | data | list[dict] または list[list] | はい |
| 3 | message | str または None | いいえ |
| 4 | chart | dict または None | いいえ |
| 5 | report_summary | list[dict] または None | いいえ |
| 6 | skip_total_rows | bool | いいえ |
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 Type | DocType の集計 | ドキュメントの件数/合計 |
| Report | Script/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)
関連項目
- references/examples.md — レポートの完全な例
- references/anti-patterns.md — よくある間違い
- references/workflows.md — 手順ごとのワークフロー
frappe-syntax-api— Frappe Python API リファレンスfrappe-core-database— データベースクエリのパターン