mcpField
F
frappe-printing-templates

frappe-printing-templates

github.com/lubusin文档处理69208 次安装12 周前更新
代码仓库

关于 frappe-printing-templates

Frappe Printing & Templates 是用于 Frappe 文档呈现的技能,面向需要制作打印格式、邮件模板和文档模板的开发者。它涵盖 Jinja 模板、PDF 生成、信头配置及输出验证。

主要功能

  1. 01创建 Jinja 打印格式
  2. 02制作动态邮件模板
  3. 03从文档生成 PDF
  4. 04配置信头与品牌元素
  5. 05检查渲染和转义风险

适用场景

  1. 01定制 DocType 文档的打印布局
  2. 02生成包含动态字段的订单邮件
  3. 03通过程序生成单据 PDF
  4. 04排查模板字段或 PDF 输出问题

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/frappe-printing-templates确认已加载。

附带文件references/jinja.mdreferences/printing.md

详细介绍

Frappe 打印与模板

在 Frappe 中使用 Jinja 创建打印格式、电子邮件模板和文档模板。

适用场景

  • 为文档创建自定义打印格式
  • 使用动态内容构建电子邮件模板
  • 根据文档生成 PDF
  • 在网页中使用 Jinja 模板
  • 配置用于品牌展示的信头
  • 使用打印格式构建器

所需输入

  • 打印格式的目标 DocType
  • 布局要求(字段、表格、页眉)
  • 格式是标准格式(受版本控制)还是自定义格式(存储在数据库中)
  • 信头/品牌展示要求
  • PDF 生成需求

流程

0) 选择格式类型
类型创建方式受版本控制用户可自定义
标准格式开发者模式,保存为 JSON是否
打印格式构建器拖放式 UI否(数据库)是
自定义 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>

验证

  • 打印格式在打印视图中正确呈现
  • 所有字段均以正确格式显示
  • PDF 生成无错误
  • 电子邮件模板使用正确的数据呈现
  • 信头显示在打印文档中
  • 模板中的翻译正常工作(_())
  • 不存在未转义内容导致的 XSS 风险

故障模式/调试

  • 模板语法错误:检查 Jinja 分隔符({{ }}、{% %});查找未闭合的代码块
  • 字段未呈现:确认字段名与 DocType 架构一致;检查子表的访问方式
  • PDF 生成失败:检查 wkhtmltopdf 是否已安装;确认打印格式中的 Jinja 有效
  • PDF 样式问题:使用内联样式;避免复杂 CSS;先在打印视图中测试
  • 模板权限错误:使用 frappe.get_all(不检查权限)而不是 frappe.get_list

升级处理

  • 应用级钩子和结构问题 → frappe-app-development
  • DocType 架构问题 → frappe-doctype-development

参考资料

防护措施

  • 使用实际数据测试:始终用真实文档预览;边缘情况可能导致模板出错
  • 妥善处理缺失字段:使用 {{ doc.field or '' }} 或 {% if doc.field %}
  • 使用 get_url() 获取图像:切勿硬编码 URL;使用 {{ frappe.utils.get_url() }}/files/...
  • 转义用户内容:对用户生成的内容使用 {{ value | e }},以防止 XSS
  • 保持样式内联:PDF 生成器不支持外部 CSS;请使用内联 style 属性

常见错误

错误失败原因修正方法
Jinja 语法错误模板报错,输出为空输出使用 {{ }},逻辑使用 {% %};检查结束标签
缺少过滤器显示原始数据使用 frappe.format() 或 frappe.format_date() 进行格式化
URL 硬编码图片/链接在不同站点上失效使用 {{ frappe.utils.get_url() }} 获取绝对 URL
子表访问方式错误内容为空或报错使用 {% for item in doc.items %},而不是 doc.child_table_name
打印格式中的 CSS 过于复杂PDF 中样式丢失使用内联样式、简单布局,并使用 <table> 构建结构
未处理 None 值输出中出现 'None' 字符串使用 {{ value or '' }} 或 {% if value %}

评论

支持 Markdown