mcpField
P
printing-templates

printing-templates

github.com/lubusinドキュメント69105 回インストール12 週間前に更新
リポジトリ

printing-templates について

Jinja を使って Frappe の印刷フォーマットやメール、ドキュメントのテンプレートを作成するスキルです。データ取得、書式設定、PDF 生成、レターヘッドの設定を扱います。

主な機能

  1. 01Jinja 印刷フォーマット
  2. 02動的なメールテンプレート
  3. 03プログラムによる PDF 生成
  4. 04レターヘッド設定
  5. 05Jinja フィルターとマクロ

活用シーン

  1. 01ブランドに沿った文書の印刷物作成
  2. 02Frappe ドキュメントからの PDF 生成
  3. 03データを埋め込んだメールテンプレート作成

インストール

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

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

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

同梱ファイルreferences/jinja.mdreferences/printing.md

詳細

Frappe の印刷とテンプレート

Frappe で Jinja を使用して、印刷フォーマット、メールテンプレート、ドキュメントテンプレートを作成します。

使用するタイミング

  • ドキュメントのカスタム印刷フォーマットを作成する場合
  • 動的コンテンツを含むメールテンプレートを作成する場合
  • ドキュメントから PDF を生成する場合
  • Web ページで Jinja テンプレートを使用する場合
  • ブランドイメージに合わせてレターヘッドを設定する場合
  • Print Format Builder を使用する場合

必要な入力

  • 印刷フォーマットの対象となる DocType
  • レイアウト要件(フィールド、テーブル、ヘッダー)
  • フォーマットを標準(バージョン管理)にするか、カスタム(DB に保存)にするか
  • レターヘッド/ブランディングの要件
  • PDF 生成の要件

手順

0) フォーマットの種類を選択する
種類作成方法バージョン管理ユーザーによるカスタマイズ
標準Developer Mode で JSON として保存はいいいえ
Print Format Builderドラッグ&ドロップ UIいいえ(DB)はい
カスタム HTML(Jinja)awesomebar に「new print format」と入力オプション場合による
1) Jinja 印刷フォーマットを作成する

awesomebar → 「New Print Format」から作成します:

  1. 一意の名前を設定します
  2. 対象の DocType にリンクします
  3. 「Standard」=「No」に設定します(開発モードでエクスポートする場合は「Yes」)
  4. 「Custom Format」にチェックを入れます
  5. Print Format Type = 「Jinja」に設定します
  6. Jinja HTML を記述します
<div class="print-format">
    <h1>{{ doc.name }}</h1>
    <p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p>
    <p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p>

    <table class="table table-bordered">
        <thead>
            <tr>
                <th>{{ _("Item") }}</th>
                <th>{{ _("Qty") }}</th>
                <th class="text-right">{{ _("Rate") }}</th>
                <th class="text-right">{{ _("Amount") }}</th>
            </tr>
        </thead>
        <tbody>
            {% for item in doc.items %}
            <tr>
                <td>{{ item.item_name }}</td>
                <td>{{ item.qty }}</td>
                <td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td>
                <td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td>
            </tr>
            {% endfor %}
        </tbody>
        <tfoot>
            <tr>
                <td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td>
                <td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td>
            </tr>
        </tfoot>
    </table>

    {% if doc.terms %}
    <div class="terms">
        <h4>{{ _("Terms & Conditions") }}</h4>
        <p>{{ doc.terms }}</p>
    </div>
    {% endif %}
</div>

<style>
    .print-format { font-family: Arial, sans-serif; }
    .print-format h1 { color: #333; }
    .print-format table { width: 100%; margin-top: 20px; }
</style>
2) Frappe Jinja API を使用する

テンプレートでのデータ取得:

{# Fetch a document #}
{% set customer = frappe.get_doc('Customer', doc.customer) %}
{{ customer.customer_name }}

{# List query (ignores permissions) #}
{% set open_orders = frappe.get_all('Sales Order',
    filters={'customer': doc.customer, 'status': 'To Deliver and Bill'},
    fields=['name', 'grand_total'],
    order_by='creation desc',
    page_length=5) %}

{# Permission-aware list query #}
{% set my_tasks = frappe.get_list('Task',
    filters={'owner': frappe.session.user}) %}

{# Single value lookup #}
{% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %}

{# Settings value #}
{% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %}

書式設定:

{{ frappe.format(50000, {'fieldtype': 'Currency'}) }}
{{ frappe.format_date('2025-01-15') }}
{{ frappe.format_date(doc.posting_date) }}

セッションとコンテキスト:

{{ frappe.session.user }}
{{ frappe.get_fullname() }}
{{ frappe.lang }}
{{ _("Translatable string") }}

URL:

<a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a>
3) メールテンプレートを作成する
Dear {{ doc.customer_name }},

Your order {{ doc.name }} has been confirmed.

Items:
{% for item in doc.items %}
- {{ item.item_name }} x {{ item.qty }}
{% endfor %}

Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}

Thank you,
{{ frappe.get_fullname() }}
4) プログラムから PDF を生成する
import frappe

# Generate PDF
pdf_content = frappe.get_print(
    doctype="Sales Invoice",
    name="SINV-001",
    print_format="Custom Invoice",
    as_pdf=True
)

# Attach PDF to document
frappe.attach_print(
    doctype="Sales Invoice",
    name="SINV-001",
    print_format="Custom Invoice",
    file_name="invoice.pdf"
)

# Send with email
frappe.sendmail(
    recipients=["[email protected]"],
    subject="Your Invoice",
    message="Please find attached your invoice.",
    attachments=[{
        "fname": "invoice.pdf",
        "fcontent": pdf_content
    }]
)
5) レターヘッドを設定する
  1. レターヘッドの一覧に移動し、「New」を選択します
  2. 会社のロゴとヘッダー画像をアップロードします
  3. 会社のデフォルトとして設定します
  4. レターヘッドは印刷フォーマットに自動的に表示されます
6) Jinja フィルターを使用する
{{ doc.customer_name|upper }}        {# UPPERCASE #}
{{ doc.notes|truncate(100) }}        {# Truncate text #}
{{ doc.description|striptags }}      {# Remove HTML #}
{{ doc.html_content|safe }}          {# Render raw HTML (trusted only!) #}
{{ items|length }}                   {# Count items #}
{{ items|first }}                    {# First item #}
{{ names|join(', ') }}               {# Join list #}
{{ amount|round(2) }}               {# Round number #}
{{ value|default('N/A') }}          {# Default if undefined #}
{{ data|tojson }}                    {# Convert to JSON #}
7) テンプレートの継承とマクロ
{# macros/fields.html #}
{% macro field_row(label, value) %}
<tr>
    <td class="label"><strong>{{ _(label) }}</strong></td>
    <td>{{ value }}</td>
</tr>
{% endmacro %}

{# In print format #}
{% from "macros/fields.html" import field_row %}
<table>
    {{ field_row("Customer", doc.customer_name) }}
    {{ field_row("Date", frappe.format_date(doc.posting_date)) }}
    {{ field_row("Total", frappe.format(doc.grand_total, {'fieldtype': 'Currency'})) }}
</table>

検証

  • Print View で印刷フォーマットが正しく表示される
  • すべてのフィールドが適切な書式で表示される
  • エラーなく PDF を生成できる
  • メールテンプレートに正しいデータが表示される
  • 印刷したドキュメントにレターヘッドが表示される
  • テンプレートで翻訳が機能する(_())
  • エスケープされていないコンテンツによる XSS のリスクがない

失敗時の対応/デバッグ

  • テンプレート構文エラー:Jinja の区切り文字({{ }}、{% %})を確認し、閉じていないブロックがないか調べます
  • フィールドが表示されない:フィールド名が DocType のスキーマと一致していることを確認し、子テーブルへのアクセス方法を確認します
  • PDF 生成に失敗する:wkhtmltopdf がインストールされているか確認し、印刷フォーマットの Jinja が有効であることを確認します
  • PDF のスタイルに問題がある:インラインスタイルを使用し、複雑な CSS は避けます。まず Print View でテストします
  • テンプレートで権限エラーが発生する:frappe.get_list の代わりに frappe.get_all(権限チェックなし)を使用します

エスカレーション

  • アプリレベルのフックと構造について → app-development
  • DocType のスキーマに関する質問 → doctype-development

参考資料

ガードレール

  • 実データでテストする:必ず実際のドキュメントでプレビューします。エッジケースによってテンプレートが崩れることがあります
  • フィールドがない場合も適切に処理する:{{ doc.field or '' }} または {% if doc.field %} を使用します
  • 画像には get_url() を使用する:URL をハードコードせず、{{ frappe.utils.get_url() }}/files/... を使用します
  • ユーザーコンテンツをエスケープする:XSS を防ぐため、ユーザーが生成したコンテンツには {{ value | e }} を使用します
  • スタイルはインラインにする:PDF ジェネレーターは外部 CSS をサポートしていないため、インラインの style 属性を使用します

よくある間違い

間違い失敗する理由修正方法
Jinja の構文が間違っているテンプレートエラーが発生し、出力が空になります出力には {{ }}、ロジックには {% %} を使用し、閉じタグを確認します
フィルターが不足している生データが表示されます書式設定には frappe.format() または frappe.format_date() を使用します
URL をハードコードしているサイト間で画像やリンクが機能しなくなります絶対 URL には {{ frappe.utils.get_url() }} を使用します
子テーブルへのアクセス方法が間違っている空の結果になるか、エラーが発生しますdoc.child_table_name ではなく {% for item in doc.items %} を使用します
印刷形式で複雑な CSS を使用しているPDF でスタイルが失われますインラインスタイルとシンプルなレイアウトを使用し、構造には <table> を使用します
None 値を処理していない出力に 'None' という文字列が表示されます{{ value or '' }} または {% if value %} を使用します

コメント