erpnext-impl-jinja
29
总安装量
9
周安装量
#12896
全站排名
安装命令
npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-impl-jinja
Agent 安装分布
claude-code
7
opencode
5
github-copilot
5
codex
5
amp
5
Skill 文档
ERPNext Jinja Templates – Implementation
This skill helps you determine HOW to implement Jinja templates. For exact syntax, see erpnext-syntax-jinja.
Version: v14/v15/v16 compatible (with V16-specific features noted)
Main Decision: What Are You Trying to Create?
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â WHAT DO YOU WANT TO CREATE? â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¤
â â
â ⺠Printable document (invoice, PO, report)? â
â âââ Standard DocType â Print Format (Jinja) â
â âââ Query/Script Report â Report Print Format (JavaScript!) â
â â
â ⺠Automated email with dynamic content? â
â âââ Email Template (Jinja) â
â â
â ⺠Customer-facing web page? â
â âââ Portal Page (www/*.html + *.py) â
â â
â ⺠Reusable template functions/filters? â
â âââ Custom jenv methods in hooks.py â
â â
â ⺠Notification content? â
â âââ Notification Template (uses Jinja syntax) â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ï¸ CRITICAL: Report Print Formats use JAVASCRIPT templating, NOT Jinja!
- Jinja: {{ variable }}
- JS Report: {%= variable %}
Decision Tree: Print Format Type
WHAT ARE YOU PRINTING?
â
ââ⺠Standard DocType (Invoice, PO, Quotation)?
â â
â â WHERE TO CREATE?
â ââ⺠Quick/simple format â Print Format Builder (Setup > Print)
â â - Drag-drop interface
â â - Limited customization
â â
â ââ⺠Complex layout needed â Custom HTML Print Format
â - Full Jinja control
â - Custom CSS styling
â - Dynamic logic
â
ââ⺠Query Report or Script Report?
â ââ⺠Report Print Format (JAVASCRIPT template!)
â â ï¸ NOT Jinja! Uses {%= %} and {% %}
â
ââ⺠Letter or standalone document?
ââ⺠Letter Head + Print Format combination
Decision Tree: Where to Store Template
IS THIS A ONE-OFF OR REUSABLE?
â
ââ⺠Site-specific, managed via UI?
â ââ⺠Create via Setup > Print Format / Email Template
â - Stored in database
â - Easy to edit without code
â
ââ⺠Part of your custom app?
â â
â â WHAT TYPE?
â ââ⺠Print Format â myapp/fixtures or db records
â â
â ââ⺠Portal Page â myapp/www/pagename/
â â - index.html (template)
â â - index.py (context)
â â
â ââ⺠Custom methods/filters â myapp/jinja/
â - Registered via hooks.py jenv
â
ââ⺠Template for multiple sites?
ââ⺠Include in app, export as fixture
Implementation Workflow: Print Format
Step 1: Create via UI (Recommended Start)
Setup > Printing > Print Format > New
- DocType: Sales Invoice
- Module: Accounts
- Standard: No (Custom)
- Print Format Type: Jinja
Step 2: Basic Template Structure
{# ALWAYS include styles at top #}
<style>
.print-format { font-family: Arial, sans-serif; }
.header { background: #f5f5f5; padding: 15px; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { border: 1px solid #ddd; padding: 8px; }
.text-right { text-align: right; }
.footer { margin-top: 30px; border-top: 1px solid #ddd; }
</style>
{# Document header #}
<div class="header">
<h1>{{ doc.select_print_heading or _("Invoice") }}</h1>
<p><strong>{{ doc.name }}</strong></p>
<p>{{ _("Date") }}: {{ doc.get_formatted("posting_date") }}</p>
</div>
{# Items table #}
<table class="table">
<thead>
<tr>
<th>{{ _("Item") }}</th>
<th class="text-right">{{ _("Qty") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
</thead>
<tbody>
{% for row in doc.items %}
<tr>
<td>{{ row.item_name }}</td>
<td class="text-right">{{ row.qty }}</td>
<td class="text-right">{{ row.get_formatted("amount", doc) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{# Totals #}
<div class="text-right">
<p><strong>{{ _("Grand Total") }}:</strong> {{ doc.get_formatted("grand_total") }}</p>
</div>
Step 3: Test and Refine
1. Open a document (e.g., Sales Invoice)
2. Menu > Print > Select your format
3. Check layout, adjust CSS as needed
4. Test PDF generation
Implementation Workflow: Email Template
Step 1: Create via UI
Setup > Email > Email Template > New
- Name: Payment Reminder
- Subject: Invoice {{ doc.name }} - Payment Due
- DocType: Sales Invoice
Step 2: Template Content
<p>{{ _("Dear") }} {{ doc.customer_name }},</p>
<p>{{ _("This is a reminder that invoice") }} <strong>{{ doc.name }}</strong>
{{ _("for") }} {{ doc.get_formatted("grand_total") }} {{ _("is due.") }}</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">
<strong>{{ _("Due Date") }}</strong>
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{{ frappe.format_date(doc.due_date) }}
</td>
</tr>
<tr>
<td style="padding: 8px; border: 1px solid #ddd;">
<strong>{{ _("Outstanding") }}</strong>
</td>
<td style="padding: 8px; border: 1px solid #ddd;">
{{ doc.get_formatted("outstanding_amount") }}
</td>
</tr>
</table>
{% if doc.items %}
<p><strong>{{ _("Items") }}:</strong></p>
<ul>
{% for item in doc.items %}
<li>{{ item.item_name }} ({{ item.qty }})</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ _("Best regards") }},<br>
{{ frappe.db.get_value("Company", doc.company, "company_name") }}</p>
Step 3: Use in Notifications or Code
# In Server Script or Controller
frappe.sendmail(
recipients=[doc.email],
subject=frappe.render_template(
frappe.db.get_value("Email Template", "Payment Reminder", "subject"),
{"doc": doc}
),
message=frappe.get_template("Payment Reminder").render({"doc": doc})
)
Implementation Workflow: Portal Page
Step 1: Create Directory Structure
myapp/
âââ www/
âââ projects/
âââ index.html # Jinja template
âââ index.py # Python context
Step 2: Create Template (index.html)
{% extends "templates/web.html" %}
{% block title %}{{ _("Projects") }}{% endblock %}
{% block page_content %}
<div class="container">
<h1>{{ title }}</h1>
{% if frappe.session.user != 'Guest' %}
<p>{{ _("Welcome") }}, {{ frappe.get_fullname() }}</p>
{% endif %}
<div class="row">
{% for project in projects %}
<div class="col-md-4">
<div class="card">
<h3>{{ project.title }}</h3>
<p>{{ project.description | truncate(100) }}</p>
<a href="/projects/{{ project.name }}">{{ _("View Details") }}</a>
</div>
</div>
{% else %}
<p>{{ _("No projects found.") }}</p>
{% endfor %}
</div>
</div>
{% endblock %}
Step 3: Create Context (index.py)
import frappe
def get_context(context):
context.title = "Projects"
context.no_cache = True # Dynamic content
# Fetch data
context.projects = frappe.get_all(
"Project",
filters={"is_public": 1},
fields=["name", "title", "description"],
order_by="creation desc"
)
return context
Step 4: Test
Visit: https://yoursite.com/projects
Implementation Workflow: Custom Jinja Methods
Step 1: Register in hooks.py
# myapp/hooks.py
jenv = {
"methods": ["myapp.jinja.methods"],
"filters": ["myapp.jinja.filters"]
}
Step 2: Create Methods Module
# myapp/jinja/methods.py
import frappe
def get_company_logo(company):
"""Returns company logo URL - usable in any template"""
return frappe.db.get_value("Company", company, "company_logo") or ""
def get_address_display(address_name):
"""Format address for display"""
if not address_name:
return ""
return frappe.get_doc("Address", address_name).get_display()
def get_outstanding_amount(customer):
"""Get total outstanding for customer"""
result = frappe.db.sql("""
SELECT COALESCE(SUM(outstanding_amount), 0)
FROM `tabSales Invoice`
WHERE customer = %s AND docstatus = 1
""", customer)
return result[0][0] if result else 0
Step 3: Create Filters Module
# myapp/jinja/filters.py
def format_phone(value):
"""Format phone number: 1234567890 â (123) 456-7890"""
if not value:
return ""
digits = ''.join(c for c in str(value) if c.isdigit())
if len(digits) == 10:
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
return value
def currency_words(amount, currency="EUR"):
"""Convert number to words (simplified)"""
return f"{currency} {amount:,.2f}"
Step 4: Use in Templates
{# Methods - called as functions #}
<img src="{{ get_company_logo(doc.company) }}" alt="Logo">
<p>{{ get_address_display(doc.customer_address) }}</p>
<p>Outstanding: {{ get_outstanding_amount(doc.customer) }}</p>
{# Filters - piped after values #}
<p>Phone: {{ doc.phone | format_phone }}</p>
<p>Amount: {{ doc.grand_total | currency_words }}</p>
Step 5: Deploy
bench --site sitename migrate
Quick Reference: Context Variables
| Template Type | Available Objects |
|---|---|
| Print Format | doc, frappe, _() |
| Email Template | doc, frappe (limited) |
| Portal Page | frappe.session, frappe.form_dict, custom context |
| Notification | doc, frappe |
Quick Reference: Essential Methods
| Need | Method |
|---|---|
| Format currency/date | doc.get_formatted("fieldname") |
| Format child row | row.get_formatted("field", doc) |
| Translate string | _("String") |
| Get linked doc | frappe.get_doc("DocType", name) |
| Get single field | frappe.db.get_value("DT", name, "field") |
| Current date | frappe.utils.nowdate() |
| Format date | frappe.format_date(date) |
Critical Rules
1. ALWAYS use get_formatted for display values
{# â Raw database value #}
{{ doc.grand_total }}
{# â
Properly formatted with currency #}
{{ doc.get_formatted("grand_total") }}
2. ALWAYS pass parent doc for child table formatting
{% for row in doc.items %}
{# â Missing currency context #}
{{ row.get_formatted("rate") }}
{# â
Has currency context from parent #}
{{ row.get_formatted("rate", doc) }}
{% endfor %}
3. ALWAYS use translation function for user text
{# â Not translatable #}
<h1>Invoice</h1>
{# â
Translatable #}
<h1>{{ _("Invoice") }}</h1>
4. NEVER use Jinja in Report Print Formats
<!-- Query/Script Reports use JAVASCRIPT templating -->
{% for(var i=0; i<data.length; i++) { %}
<tr><td>{%= data[i].name %}</td></tr>
{% } %}
5. NEVER execute queries in loops
{# â N+1 query problem #}
{% for item in doc.items %}
{% set stock = frappe.db.get_value("Bin", ...) %}
{% endfor %}
{# â
Prefetch data in controller/context #}
{% for item in items_with_stock %}
{{ item.stock_qty }}
{% endfor %}
Version Differences
| Feature | V14 | V15 | V16 |
|---|---|---|---|
| Jinja templates | â | â | â |
| get_formatted() | â | â | â |
| jenv hooks | â | â | â |
| wkhtmltopdf PDF | â | â | â ï¸ |
| Chrome PDF | â | â | â |
V16 Chrome PDF Considerations
See erpnext-syntax-jinja for detailed Chrome PDF documentation.
Reference Files
| File | Contents |
|---|---|
| decision-tree.md | Complete template type selection |
| workflows.md | Step-by-step implementation patterns |
| examples.md | Complete working examples |
| anti-patterns.md | Common mistakes to avoid |