--- name: erpnext-impl-jinja description: "Implementation workflows and decision trees for Jinja templates in ERPNext/Frappe. Use when determining HOW to implement Print Formats, Email Templates, Portal Pages, or custom Jinja methods. Covers template type selection, context variables, styling, and V16 Chrome PDF rendering. Triggers: create print format, email template, portal page, custom jinja filter, print format styling, pdf template, invoice template, report template." --- # 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 ```jinja {# ALWAYS include styles at top #} {# Document header #}
{{ doc.name }}
{{ _("Date") }}: {{ doc.get_formatted("posting_date") }}
| {{ _("Item") }} | {{ _("Qty") }} | {{ _("Amount") }} |
|---|---|---|
| {{ row.item_name }} | {{ row.qty }} | {{ row.get_formatted("amount", doc) }} |
{{ _("Grand Total") }}: {{ doc.get_formatted("grand_total") }}
{{ _("Dear") }} {{ doc.customer_name }},
{{ _("This is a reminder that invoice") }} {{ doc.name }} {{ _("for") }} {{ doc.get_formatted("grand_total") }} {{ _("is due.") }}
| {{ _("Due Date") }} | {{ frappe.format_date(doc.due_date) }} |
| {{ _("Outstanding") }} | {{ doc.get_formatted("outstanding_amount") }} |
{{ _("Items") }}:
{{ _("Best regards") }},
{{ frappe.db.get_value("Company", doc.company, "company_name") }}
{{ _("Welcome") }}, {{ frappe.get_fullname() }}
{% endif %}{{ _("No projects found.") }}
{% endfor %}{{ get_address_display(doc.customer_address) }}
Outstanding: {{ get_outstanding_amount(doc.customer) }}
{# Filters - piped after values #}Phone: {{ doc.phone | format_phone }}
Amount: {{ doc.grand_total | currency_words }}
``` ### Step 5: Deploy ```bash 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 ```jinja {# ❌ Raw database value #} {{ doc.grand_total }} {# ✅ Properly formatted with currency #} {{ doc.get_formatted("grand_total") }} ``` ### 2. ALWAYS pass parent doc for child table formatting ```jinja {% 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 ```jinja {# ❌ Not translatable #}