Skill125 estrellas del repoactualizado 2mo ago
frappe-impl-jinja
>
Instalar en Claude Code
Copiargit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-impl-jinja && cp -r /tmp/frappe-impl-jinja/skills/source/impl/frappe-impl-jinja ~/.claude/skills/frappe-impl-jinjaDespués abre una sesión nueva de Claude Code; el skill carga automáticamente.
Definición
SKILL.md
# Frappe Jinja Templates Implementation Workflow
Step-by-step workflows for building Jinja templates. For syntax reference, see `frappe-syntax-jinja`.
**Version**: v14/v15/v16 (V16 Chrome PDF noted)
---
## Master Decision: What Are You Creating?
```
WHAT IS YOUR OUTPUT?
│
├─► Printable PDF (invoice, PO, report)?
│ ├─► Standard DocType → Print Format (Jinja)
│ └─► Query/Script Report → Report Print Format (JAVASCRIPT!)
│ ⚠️ Uses {%= %} NOT {{ }}
│
├─► Automated email with dynamic content?
│ └─► Email Template (Jinja, linked to DocType)
│
├─► System notification?
│ └─► Notification (Setup > Notification, uses Jinja)
│
├─► Customer-facing web page?
│ └─► Portal Page (myapp/www/*.html + *.py)
│
└─► Reusable template functions/filters?
└─► Custom jenv methods in hooks.py
```
---
## Workflow 1: Create a Print Format
### Step 1: Create via UI
```
Setup > Printing > Print Format > New
- Name: My Invoice Format
- DocType: Sales Invoice
- Module: Accounts
- Standard: No (custom)
- Print Format Type: Jinja
```
### Step 2: Write the Template
```jinja
<style>
.print-format { font-family: Arial, sans-serif; font-size: 11px; }
.header { margin-bottom: 20px; }
.table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.table th, .table td { border: 1px solid #ddd; padding: 8px; }
.table th { background: #f0f0f0; }
.text-right { text-align: right; }
</style>
<div class="header">
<h1>{{ doc.select_print_heading or _("Invoice") }}</h1>
<p><strong>{{ doc.name }}</strong> |
{{ doc.get_formatted("posting_date") }}</p>
</div>
<p><strong>{{ doc.customer_name }}</strong></p>
{% if doc.address_display %}
<p>{{ doc.address_display | safe }}</p>
{% endif %}
<table class="table">
<thead>
<tr>
<th>#</th>
<th>{{ _("Item") }}</th>
<th class="text-right">{{ _("Qty") }}</th>
<th class="text-right">{{ _("Rate") }}</th>
<th class="text-right">{{ _("Amount") }}</th>
</tr>
</thead>
<tbody>
{% for row in doc.items %}
<tr>
<td>{{ row.idx }}</td>
<td>{{ row.item_name }}</td>
<td class="text-right">{{ row.qty }}</td>
<td class="text-right">{{ row.get_formatted("rate", doc) }}</td>
<td class="text-right">{{ row.get_formatted("amount", doc) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for tax in doc.taxes %}
<p class="text-right">{{ tax.description }}: {{ tax.get_formatted("tax_amount", doc) }}</p>
{% endfor %}
<p class="text-right">
<strong>{{ _("Grand Total") }}: {{ doc.get_formatted("grand_total") }}</strong>
</p>
{% if doc.terms %}
<div style="margin-top: 30px; border-top: 1px solid #ddd; padding-top: 10px;">
<strong>{{ _("Terms and Conditions") }}</strong>
{{ doc.terms | safe }}
</div>
{% endif %}
```
### Step 3: Test
1. Open a Sales Invoice
2. Menu > Print > Select "My Invoice Format"
3. Verify layout and formatting
4. **ALWAYS** test PDF download — wkhtmltopdf renders differently from browser
### Critical Rules for Print Formats
- **ALWAYS** use `doc.get_formatted("field")` for currency, dates, numbers
- **ALWAYS** pass parent doc for child rows: `row.get_formatted("rate", doc)`
- **ALWAYS** wrap user-facing text with `_("text")` for translation
- **ALWAYS** put CSS in a `<style>` block at the top (not external files)
- **NEVER** use flexbox in v14/v15 (wkhtmltopdf does not support it) — V16 Chrome PDF does
- **NEVER** use `| safe` on user-supplied input — only on trusted system HTML
---
## Workflow 2: Create an Email Template
### Step 1: Create via UI
```
Setup > Email > Email Template > New
- Name: Payment Reminder
- Subject: Invoice {{ doc.name }} - Payment Reminder
- DocType: Sales Invoice
```
### Step 2: Write Email Content
**ALWAYS** use inline styles for emails — most clients strip `<style>` blocks.
```jinja
<div style="font-family: Arial, sans-serif; max-width: 600px;">
<p>{{ _("Dear") }} {{ doc.customer_name }},</p>
<p>{{ _("Invoice") }} <strong>{{ doc.name }}</strong>
{{ _("for") }} {{ doc.get_formatted("grand_total") }}
{{ _("is due for payment.") }}</p>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
<tr style="background: #f5f5f5;">
<td style="padding: 10px; border: 1px solid #ddd;">
<strong>{{ _("Due Date") }}</strong></td>
<td style="padding: 10px; border: 1px solid #ddd;">
{{ frappe.format_date(doc.due_date) }}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #ddd;">
<strong>{{ _("Outstanding") }}</strong></td>
<td style="padding: 10px; border: 1px solid #ddd; color: #c00;">
{{ doc.get_formatted("outstanding_amount") }}</td>
</tr>
</table>
{% if doc.items %}
<p><strong>{{ _("Items") }}:</strong></p>
<ul>
{% for item in doc.items[:5] %}
<li>{{ item.item_name }} ({{ item.qty }})</li>
{% endfor %}
{% if doc.items | length > 5 %}
<li style="color: #666;">{{ _("and {0} more...").format(doc.items|length - 5) }}</li>
{% endif %}
</ul>
{% endif %}
<p>{{ _("Best regards") }},<br>
{{ frappe.db.get_value("Company", doc.company, "company_name") }}</p>
</div>
```
### Step 3: Use in Notification or Code
**Option A: Auto-triggered Notification**
```
Setup > Notification > New
- Channel: Email
- Document Type: Sales Invoice
- Send Alert On: Days After (7 days after due_date)
- Condition: doc.outstanding_amount > 0
- Email Template: Payment Reminder
```
**Option B: Send from code**
```python
template = frappe.get_doc("Email Template", "Payment Reminder")
frappe.sendmail(
recipients=[doc.contact_email],
subject=frappe.render_template(template.subject, {"doc": doc}),
message=frappe.render_template(template.response, {"doc": doc}),
reference_doctype=do