erpnext-impl-jinja

安装

npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-impl-jinja
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

{# Document header #}

{{ doc.select_print_heading or _("Invoice") }}

{{ doc.name }}

{{ _("Date") }}: {{ doc.get_formatted("posting_date") }}

{# Items table #}

{% for row in doc.items %} {% endfor %}
{{ _("Item") }} {{ _("Qty") }} {{ _("Amount") }}
{{ row.item_name }} {{ row.qty }} {{ row.get_formatted("amount", doc) }}

{# Totals #}

{{ _("Grand Total") }}: {{ doc.get_formatted("grand_total") }}

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

{{ _("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") }}

{% if doc.items %}

{{ _("Items") }}:

    {% for item in doc.items %}
  • {{ item.item_name }} ({{ item.qty }})
  • {% endfor %}

{% endif %}

{{ _("Best regards") }},
{{ frappe.db.get_value("Company", doc.company, "company_name") }}

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 %}

{{ title }}

{% if frappe.session.user != 'Guest' %}

{{ _("Welcome") }}, {{ frappe.get_fullname() }}

{% endif %}
{% for project in projects %}

{{ project.title }}

{{ project.description | truncate(100) }}

{{ _("View Details") }}
{% else %}

{{ _("No projects found.") }}

{% endfor %}

{% 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 #} Logo

{{ 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 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

Invoice

{# ✅ Translatable #}

{{ _("Invoice") }}

  1. NEVER use Jinja in Report Print Formats

{% for(var i=0; i < data.length; i++) { %}

< td > {%= data[i].name %}

{% } %} 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

返回排行榜