From 5c15b6637b184701bc35d665f4e7d5c98a4ad0bd Mon Sep 17 00:00:00 2001 From: kurogeek Date: Wed, 21 Oct 2026 03:36:46 +0000 Subject: [PATCH] feat: addresses print in the document's language Address lines, city, county and state are translatable (Property Setters), so the Address form offers a translate button that stores the other language as a Translation record. The Thailand Address Template renders those fields through _(), and the print formats re-render party addresses from the linked Address with get_print_address instead of printing the stored address_display, which is frozen in the language of whoever saved the document. The letter head already rendered the company address at print time. The withholding tax certificate takes the Thai translation whatever the print language, like its tax in words. --- README.md | 11 ++ .../default_thai_company/custom/address.json | 124 ++++++++++++++++++ .../fixtures/address_template.json | 4 +- .../fixtures/print_format.json | 24 ++-- default_thai_company/hooks.py | 1 + default_thai_company/tax_withholding.py | 13 +- default_thai_company/utils.py | 12 ++ 7 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 default_thai_company/default_thai_company/custom/address.json diff --git a/README.md b/README.md index b9fe9e6..109684e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,17 @@ file overrides the upstream Thai for the standard labels the print formats show. print formats embed Sarabun (`public/fonts`, OFL) so PDFs render Thai on servers without a Thai system font. Switch a user or the print language to `th` to use it. +### Addresses in more than one language + +Address lines, city, county and state are *translatable*: the Address form shows a translate +button next to each, which stores the other language in a Translation record. The Thailand +Address Template renders those fields through `_()`, so the print formats and letter head +show the company's and the party's address in the document's print language (the company +address is re-rendered at print time; party addresses are re-rendered from the linked +Address instead of printing the stored `address_display`). The withholding tax certificate +always takes the Thai translation. Translations are keyed on the exact text, so one +translation of a city or province serves every address that uses it. + ### Withholding tax certificate A paid Purchase Invoice with *Apply Tax Withholding Amount* gets a **Withholding Tax Certificate** diff --git a/default_thai_company/default_thai_company/custom/address.json b/default_thai_company/default_thai_company/custom/address.json new file mode 100644 index 0000000..1d9d932 --- /dev/null +++ b/default_thai_company/default_thai_company/custom/address.json @@ -0,0 +1,124 @@ +{ + "custom_fields": [], + "custom_perms": [], + "doctype": "Address", + "links": [], + "property_setters": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2026-10-20 12:00:00", + "default_value": null, + "doc_type": "Address", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "address_line1", + "idx": 0, + "is_system_generated": 0, + "modified": "2026-10-20 12:00:00", + "modified_by": "Administrator", + "module": null, + "name": "Address-address_line1-translatable", + "owner": "Administrator", + "property": "translatable", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2026-10-20 12:00:00", + "default_value": null, + "doc_type": "Address", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "address_line2", + "idx": 0, + "is_system_generated": 0, + "modified": "2026-10-20 12:00:00", + "modified_by": "Administrator", + "module": null, + "name": "Address-address_line2-translatable", + "owner": "Administrator", + "property": "translatable", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2026-10-20 12:00:00", + "default_value": null, + "doc_type": "Address", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "city", + "idx": 0, + "is_system_generated": 0, + "modified": "2026-10-20 12:00:00", + "modified_by": "Administrator", + "module": null, + "name": "Address-city-translatable", + "owner": "Administrator", + "property": "translatable", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2026-10-20 12:00:00", + "default_value": null, + "doc_type": "Address", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "county", + "idx": 0, + "is_system_generated": 0, + "modified": "2026-10-20 12:00:00", + "modified_by": "Administrator", + "module": null, + "name": "Address-county-translatable", + "owner": "Administrator", + "property": "translatable", + "property_type": "Check", + "row_name": null, + "value": "1" + }, + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "creation": "2026-10-20 12:00:00", + "default_value": null, + "doc_type": "Address", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "state", + "idx": 0, + "is_system_generated": 0, + "modified": "2026-10-20 12:00:00", + "modified_by": "Administrator", + "module": null, + "name": "Address-state-translatable", + "owner": "Administrator", + "property": "translatable", + "property_type": "Check", + "row_name": null, + "value": "1" + } + ], + "sync_on_migrate": 1 +} diff --git a/default_thai_company/fixtures/address_template.json b/default_thai_company/fixtures/address_template.json index 02a47e2..5b210ae 100644 --- a/default_thai_company/fixtures/address_template.json +++ b/default_thai_company/fixtures/address_template.json @@ -4,8 +4,8 @@ "docstatus": 0, "doctype": "Address Template", "is_default": 1, - "modified": "2026-09-09 17:23:33.559202", + "modified": "2026-10-20 12:00:00.000000", "name": "Thailand", - "template": "{{ address_line1 }}{% if address_line2 %} {{ address_line2 }}{% endif %}
\n{% if county %}{{ county }} {% endif -%}{{ city }}{% if state %} {{ state }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}{% if country != \"Thailand\" %} {{ country }}{% endif %}
\n{% if phone %}{{ _(\"Phone\") }}: {{ phone }}
{% endif -%}\n{% if fax %}{{ _(\"Fax\") }}: {{ fax }}
{% endif -%}\n{% if email_id %}{{ _(\"Email\") }}: {{ email_id }}
{% endif -%}\n" + "template": "{#- Fields go through _() so Translation records (the field's translate button) render the address in the print language. -#}\n{{ _(address_line1) }}{% if address_line2 %} {{ _(address_line2) }}{% endif %}
\n{% if county %}{{ _(county) }} {% endif -%}{{ _(city) }}{% if state %} {{ _(state) }}{% endif -%}{% if pincode %} {{ pincode }}{% endif -%}{% if country != \"Thailand\" %} {{ _(country) }}{% endif %}
\n{% if phone %}{{ _(\"Phone\") }}: {{ phone }}
{% endif -%}\n{% if fax %}{{ _(\"Fax\") }}: {{ fax }}
{% endif -%}\n{% if email_id %}{{ _(\"Email\") }}: {{ email_id }}
{% endif -%}\n" } ] \ No newline at end of file diff --git a/default_thai_company/fixtures/print_format.json b/default_thai_company/fixtures/print_format.json index 11ad2e7..66856fe 100644 --- a/default_thai_company/fixtures/print_format.json +++ b/default_thai_company/fixtures/print_format.json @@ -12,13 +12,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{%- set bank_account = get_company_bank_account(doc.company) -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date / due_date are printed in the heading, customer fields in the Customer section,\n in_words in the left column of the Totals section. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"update_billed_amount_in_delivery_note\", \"amount_eligible_for_commission\", \"company_tax_id\",\n\t\"disable_rounded_total\",\n\t\"posting_date\", \"due_date\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{%- if doc.due_date %}\n\t\t
{{ doc.get_formatted(\"due_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ doc.address_display }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Sales Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last and bank_account %}\n\t
\n\t\t{%- if doc.print_line_breaks -%}
{%- endif -%}\n\t\t
\n\t\t\t

{{ _(\"Bank Account Details\") }}

\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if bank_account.bank_account_no %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.iban %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.branch_code %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.swift_number %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Receiver\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Issuer\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n\n\n\n\n\n\n\n\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{%- set bank_account = get_company_bank_account(doc.company) -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date / due_date are printed in the heading, customer fields in the Customer section,\n in_words in the left column of the Totals section. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"update_billed_amount_in_delivery_note\", \"amount_eligible_for_commission\", \"company_tax_id\",\n\t\"disable_rounded_total\",\n\t\"posting_date\", \"due_date\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{%- if doc.due_date %}\n\t\t
{{ doc.get_formatted(\"due_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.customer_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ address }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Sales Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last and bank_account %}\n\t
\n\t\t{%- if doc.print_line_breaks -%}
{%- endif -%}\n\t\t
\n\t\t\t

{{ _(\"Bank Account Details\") }}

\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if bank_account.bank_account_no %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.iban %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.branch_code %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if bank_account.swift_number %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Receiver\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Issuer\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n\n\n\n\n\n\n\n\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-15 07:10:28.117714", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Sales Invoice", "page_number": "Hide", @@ -46,13 +46,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date is printed in the heading (due_date dropped: a receipt shows the paid date), customer fields in the Customer section,\n in_words in the left column of the Totals section. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"update_billed_amount_in_delivery_note\", \"amount_eligible_for_commission\", \"company_tax_id\",\n\t\"disable_rounded_total\",\n\t\"posting_date\", \"due_date\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{#- A receipt prints the date the invoice was paid: the latest submitted Payment Entry\n\t\t that references this invoice. -#}\n\t\t{%- set payment = frappe.get_all(\"Payment Entry\",\n\t\t\tfilters=[\n\t\t\t\t[\"docstatus\", \"=\", 1],\n\t\t\t\t[\"Payment Entry Reference\", \"reference_doctype\", \"=\", doc.doctype],\n\t\t\t\t[\"Payment Entry Reference\", \"reference_name\", \"=\", doc.name],\n\t\t\t],\n\t\t\tfields=[\"posting_date\"], order_by=\"posting_date desc\", limit_page_length=1) -%}\n\t\t{%- if payment %}\n\t\t
{{ frappe.format(payment[0].posting_date, {\"fieldtype\": \"Date\"}) }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(\"Tax Invoice/Receipt\")) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ doc.address_display }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Sales Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Receiver\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Issuer\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n\n\n\n\n\n\n\n\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date is printed in the heading (due_date dropped: a receipt shows the paid date), customer fields in the Customer section,\n in_words in the left column of the Totals section. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"update_billed_amount_in_delivery_note\", \"amount_eligible_for_commission\", \"company_tax_id\",\n\t\"disable_rounded_total\",\n\t\"posting_date\", \"due_date\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{#- A receipt prints the date the invoice was paid: the latest submitted Payment Entry\n\t\t that references this invoice. -#}\n\t\t{%- set payment = frappe.get_all(\"Payment Entry\",\n\t\t\tfilters=[\n\t\t\t\t[\"docstatus\", \"=\", 1],\n\t\t\t\t[\"Payment Entry Reference\", \"reference_doctype\", \"=\", doc.doctype],\n\t\t\t\t[\"Payment Entry Reference\", \"reference_name\", \"=\", doc.name],\n\t\t\t],\n\t\t\tfields=[\"posting_date\"], order_by=\"posting_date desc\", limit_page_length=1) -%}\n\t\t{%- if payment %}\n\t\t
{{ frappe.format(payment[0].posting_date, {\"fieldtype\": \"Date\"}) }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(\"Tax Invoice/Receipt\")) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.customer_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ address }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Sales Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Receiver\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Issuer\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n\n\n\n\n\n\n\n\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-15 07:10:28.117714", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Tax Invoice/Receipt", "page_number": "Hide", @@ -80,13 +80,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on quotations with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- Lead has no Tax ID; only a Customer party carries one. -#}\n{%- set tax_id = frappe.db.get_value(\"Customer\", doc.party_name, \"tax_id\") if doc.quotation_to == \"Customer\" else None -%}\n{#- Standard layout prints every non-empty field; drop the ones this quotation does not need.\n transaction_date / valid_till are printed in the heading, customer fields in the Customer section,\n in_words in the left column of the Totals section, the company address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"company_address\", \"company_address_display\", \"has_unit_price_items\",\n\t\"disable_rounded_total\",\n\t\"transaction_date\", \"valid_till\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.valid_till %}\n\t\t
{{ doc.get_formatted(\"valid_till\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.party_name }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ doc.address_display }}
{{ _(\"Tax ID\") }}{{ tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Quotation carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 2-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.party_name }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Accepted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Quoted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on quotations with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- Lead has no Tax ID; only a Customer party carries one. -#}\n{%- set tax_id = frappe.db.get_value(\"Customer\", doc.party_name, \"tax_id\") if doc.quotation_to == \"Customer\" else None -%}\n{#- Standard layout prints every non-empty field; drop the ones this quotation does not need.\n transaction_date / valid_till are printed in the heading, customer fields in the Customer section,\n in_words in the left column of the Totals section, the company address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"company_address\", \"company_address_display\", \"has_unit_price_items\",\n\t\"disable_rounded_total\",\n\t\"transaction_date\", \"valid_till\",\n\t\"customer_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.valid_till %}\n\t\t
{{ doc.get_formatted(\"valid_till\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.customer_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.party_name }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ address }}
{{ _(\"Tax ID\") }}{{ tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Quotation carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 2-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.party_name }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Accepted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Quoted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-16 00:00:00.000000", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Quotation", "page_number": "Hide", @@ -114,13 +114,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount (withholding tax as a deduction), so the lines add up to Grand Total. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date / due_date and the supplier's invoice reference are printed in the heading,\n supplier fields in the Supplier section, in_words in the left column of the Totals section,\n the company's billing address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"tax_withholding_net_total\", \"update_outstanding_for_self\",\n\t\"update_billed_amount_in_purchase_order\", \"update_billed_amount_in_purchase_receipt\",\n\t\"billing_address\", \"billing_address_display\", \"supplier_group\", \"disable_rounded_total\",\n\t\"posting_date\", \"due_date\", \"bill_no\", \"bill_date\",\n\t\"supplier_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{%- if doc.due_date %}\n\t\t
{{ doc.get_formatted(\"due_date\") }}
\n\t\t{%- endif %}\n\t\t{%- if doc.bill_no %}\n\t\t
{{ doc.bill_no }}
\n\t\t{%- endif %}\n\t\t{%- if doc.bill_date %}\n\t\t
{{ doc.get_formatted(\"bill_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials.\n Purchase Invoice Item marks item_code print_hide (it prints item_name instead); show the code\n as the other formats do. -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\", \"print_hide\": 0},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Supplier\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"supplier_name\")) }}{{ doc.supplier_name or doc.supplier }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ doc.address_display }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Purchase Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t{#- A Purchase Invoice is the company's own record of the supplier's bill; both signatures are the company's. -#}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Prepared By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on invoices with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount (withholding tax as a deduction), so the lines add up to Grand Total. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this invoice does not need.\n posting_date / due_date and the supplier's invoice reference are printed in the heading,\n supplier fields in the Supplier section, in_words in the left column of the Totals section,\n the company's billing address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"tax_withholding_net_total\", \"update_outstanding_for_self\",\n\t\"update_billed_amount_in_purchase_order\", \"update_billed_amount_in_purchase_receipt\",\n\t\"billing_address\", \"billing_address_display\", \"supplier_group\", \"disable_rounded_total\",\n\t\"posting_date\", \"due_date\", \"bill_no\", \"bill_date\",\n\t\"supplier_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"tax_id\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"posting_date\") }}
\n\t\t{%- if doc.due_date %}\n\t\t
{{ doc.get_formatted(\"due_date\") }}
\n\t\t{%- endif %}\n\t\t{%- if doc.bill_no %}\n\t\t
{{ doc.bill_no }}
\n\t\t{%- endif %}\n\t\t{%- if doc.bill_date %}\n\t\t
{{ doc.get_formatted(\"bill_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials.\n Purchase Invoice Item marks item_code print_hide (it prints item_name instead); show the code\n as the other formats do. -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\", \"print_hide\": 0},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Supplier\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.supplier_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"supplier_name\")) }}{{ doc.supplier_name or doc.supplier }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ address }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t{% if doc.contact_person %}\n\t\t\t{#- Purchase Invoice carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
\n\t\t\t{% endif %}\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t{#- A Purchase Invoice is the company's own record of the supplier's bill; both signatures are the company's. -#}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Prepared By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-23 12:00:00.000000", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Purchase Invoice", "page_number": "Hide", @@ -148,13 +148,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on orders with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount (withholding tax as a deduction), so the lines add up to Grand Total. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Purchase Order has no Tax ID field; read the supplier's. -#}\n{%- set tax_id = frappe.db.get_value(\"Supplier\", doc.supplier, \"tax_id\") -%}\n{#- Standard layout prints every non-empty field; drop the ones this order does not need.\n transaction_date / schedule_date are printed in the heading, supplier and shipping fields in the\n Supplier section, in_words in the left column of the Totals section, the company's billing\n address in the letter head. Withholding shows as a deduction in the taxes, not as its flag. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"tax_withholding_net_total\", \"apply_tds\", \"tax_withholding_category\", \"has_unit_price_items\",\n\t\"billing_address\", \"billing_address_display\", \"disable_rounded_total\",\n\t\"transaction_date\", \"schedule_date\",\n\t\"supplier_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"shipping_address_display\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.schedule_date %}\n\t\t
{{ doc.get_formatted(\"schedule_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Supplier\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- The field's label is \"Supplier Address Details\"; the section already says Supplier. -#}\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"supplier_name\")) }}{{ doc.supplier_name or doc.supplier }}
{{ _(\"Address\") }}{{ doc.address_display }}
{{ _(\"Tax ID\") }}{{ tax_id }}
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{% if doc.contact_person %}\n\t\t\t\t\t{#- Purchase Order carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{#- Where the supplier is to deliver: the company's shipping address. -#}\n\t\t\t\t\t{% if doc.shipping_address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
{{ _(\"Shipping Address\") }}{{ doc.shipping_address_display }}
\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.supplier_name or doc.supplier }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Acknowledged By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on orders with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount (withholding tax as a deduction), so the lines add up to Grand Total. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Purchase Order has no Tax ID field; read the supplier's. -#}\n{%- set tax_id = frappe.db.get_value(\"Supplier\", doc.supplier, \"tax_id\") -%}\n{#- Standard layout prints every non-empty field; drop the ones this order does not need.\n transaction_date / schedule_date are printed in the heading, supplier and shipping fields in the\n Supplier section, in_words in the left column of the Totals section, the company's billing\n address in the letter head. Withholding shows as a deduction in the taxes, not as its flag. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"tax_withholding_net_total\", \"apply_tds\", \"tax_withholding_category\", \"has_unit_price_items\",\n\t\"billing_address\", \"billing_address_display\", \"disable_rounded_total\",\n\t\"transaction_date\", \"schedule_date\",\n\t\"supplier_name\", \"address_display\", \"contact_display\", \"contact_mobile\", \"contact_email\", \"shipping_address_display\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.schedule_date %}\n\t\t
{{ doc.get_formatted(\"schedule_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Supplier\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- The field's label is \"Supplier Address Details\"; the section already says Supplier. -#}\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.supplier_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"supplier_name\")) }}{{ doc.supplier_name or doc.supplier }}
{{ _(\"Address\") }}{{ address }}
{{ _(\"Tax ID\") }}{{ tax_id }}
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{% if doc.contact_person %}\n\t\t\t\t\t{#- Purchase Order carries the contact's mobile and email but not its phone; read that from the Contact. -#}\n\t\t\t\t\t{%- set contact_phone = frappe.db.get_value(\"Contact\", doc.contact_person, \"phone\") -%}\n\t\t\t\t\t\n\t\t\t\t\t{% if contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{#- Where the supplier is to deliver: the company's shipping address. -#}\n\t\t\t\t\t{%- set shipping_address = get_print_address(doc.shipping_address) -%}\n\t\t\t\t\t{% if shipping_address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(frappe.get_meta(\"Contact\").get_label(\"phone\")) }}{{ contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
{{ _(\"Shipping Address\") }}{{ shipping_address }}
\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.supplier_name or doc.supplier }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Acknowledged By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-23 12:00:00.000000", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Purchase Order", "page_number": "Hide", @@ -182,13 +182,13 @@ "font": null, "font_size": 14, "format_data": null, - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on orders with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this order does not need.\n transaction_date / delivery_date and the customer's PO reference are printed in the heading,\n customer and shipping fields in the Customer section, in_words in the left column of the Totals\n section, the company address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"has_unit_price_items\", \"amount_eligible_for_commission\", \"per_picked\", \"loyalty_points\",\n\t\"company_address\", \"company_address_display\", \"disable_rounded_total\",\n\t\"transaction_date\", \"delivery_date\", \"po_no\", \"po_date\",\n\t\"customer_name\", \"tax_id\", \"address_display\", \"contact_display\", \"contact_phone\", \"contact_mobile\", \"contact_email\", \"shipping_address\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.delivery_date %}\n\t\t
{{ doc.get_formatted(\"delivery_date\") }}
\n\t\t{%- endif %}\n\t\t{%- if doc.po_no %}\n\t\t
{{ doc.po_no }}
\n\t\t{%- endif %}\n\t\t{%- if doc.po_date %}\n\t\t
{{ doc.get_formatted(\"po_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ doc.address_display }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{% if doc.contact_person %}\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{#- Ship-to only when it differs from the billing address above. -#}\n\t\t\t\t\t{% if doc.shipping_address and doc.shipping_address != doc.address_display %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(doc.meta.get_label(\"contact_phone\")) }}{{ doc.contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
{{ _(\"Shipping Address\") }}{{ doc.shipping_address }}
\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Accepted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header, render_field with context -%}\n{#- ERPNext hides taxes marked \"Is this Tax included in Basic Rate?\" unless Accounts Settings\n enables \"Show Inclusive Tax in Print\"; always show them here so the tax line and\n \"Total (Without Tax)\" appear on orders with inclusive tax. -#}\n{%- if doc.get(\"taxes\", {\"included_in_print_rate\": 1}) -%}\n\t{%- set ignored = doc.flags.update({\"show_inclusive_tax_in_print\": 1}) -%}\n{%- endif -%}\n{#- App copies of ERPNext's totals includes, printing the posted figures: \"total\" is the net before\n the Additional Discount; \"taxes\" prints the discount's net share, Net Total and each charge after\n discount, so the lines add up to Grand Total whichever total the discount applies on. -#}\n{%- set ignored = doc.print_templates.update({\"total\": \"default_thai_company/templates/print_formats/includes/total.html\"}) -%}\n{%- if doc.print_templates.get(\"taxes\") -%}\n\t{%- set ignored = doc.print_templates.update({\"taxes\": \"default_thai_company/templates/print_formats/includes/taxes.html\"}) -%}\n{%- endif -%}\n{#- Standard layout prints every non-empty field; drop the ones this order does not need.\n transaction_date / delivery_date and the customer's PO reference are printed in the heading,\n customer and shipping fields in the Customer section, in_words in the left column of the Totals\n section, the company address in the letter head. -#}\n{%- set hidden_fields = [\n\t\"total_qty\", \"has_unit_price_items\", \"amount_eligible_for_commission\", \"per_picked\", \"loyalty_points\",\n\t\"company_address\", \"company_address_display\", \"disable_rounded_total\",\n\t\"transaction_date\", \"delivery_date\", \"po_no\", \"po_date\",\n\t\"customer_name\", \"tax_id\", \"address_display\", \"contact_display\", \"contact_phone\", \"contact_mobile\", \"contact_email\", \"shipping_address\",\n\t\"in_words\",\n] -%}\n{#- rounded_total keeps a value even when rounding is disabled, so the standard layout would print it. -#}\n{%- if doc.is_rounded_total_disabled() -%}{%- set ignored = hidden_fields.append(\"rounded_total\") -%}{%- endif -%}\n{#- add_header renders this string via frappe.render_template with {\"doc\": doc}, so it is kept\n unrendered with {% raw %}. Same markup as the stock heading plus a right-aligned date block. -#}\n{%- set heading_template -%}\n{% raw %}\n
\n\t\n\t
\n\t\t
{{ doc.get_formatted(\"transaction_date\") }}
\n\t\t{%- if doc.delivery_date %}\n\t\t
{{ doc.get_formatted(\"delivery_date\") }}
\n\t\t{%- endif %}\n\t\t{%- if doc.po_no %}\n\t\t
{{ doc.po_no }}
\n\t\t{%- endif %}\n\t\t{%- if doc.po_date %}\n\t\t
{{ doc.get_formatted(\"po_date\") }}
\n\t\t{%- endif %}\n\t
\n\t

\n\t\t
{{ _(doc.select_print_heading) or (_(doc.print_heading) if doc.print_heading != None else _(doc.doctype)) }}
\n\t\t{{ _(doc.sub_heading) if doc.sub_heading != None else _(doc.name) }}\n\t

\n\t
\n
\n{% endraw %}\n{%- endset -%}\n{#- Standard layout prints every non-empty child column; keep the item table to the essentials -#}\n{%- set item_columns = [\n\t{\"fieldname\": \"item_code\"},\n\t{\"fieldname\": \"description\"},\n\t{\"fieldname\": \"qty\"},\n\t{\"fieldname\": \"rate\"},\n\t{\"fieldname\": \"amount\"},\n] -%}\n\n{%- macro render_fields(fields, no_of_cols) -%}\n\t{%- for df in fields -%}\n\t\t{%- if df.fieldname not in hidden_fields -%}\n\t\t\t{%- if df.fieldname == \"items\" -%}\n\t\t\t\t{%- set df = df.as_dict() -%}\n\t\t\t\t{%- set ignored = df.update({\"visible_columns\": item_columns}) -%}\n\t\t\t{%- endif -%}\n\t\t\t{{ render_field(df, doc, no_of_cols) }}\n\t\t{%- endif -%}\n\t{%- endfor -%}\n{%- endmacro -%}\n\n{% for page in layout %}\n
\n\t
\n\t\t{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings, heading_template) }}\n\t
\n\n\t{% if loop.first %}\n\t
\n\t\t
\n\t\t\t

{{ _(\"Customer\") }}

\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t{#- Re-rendered from the Address so its translations follow the print language. -#}\n\t\t\t\t\t{%- set address = get_print_address(doc.customer_address) -%}\n\t\t\t\t\t{% if address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.tax_id %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"customer_name\")) }}{{ doc.customer_name or doc.customer }}
{{ _(doc.meta.get_label(\"address_display\")) }}{{ address }}
{{ _(doc.meta.get_label(\"tax_id\")) }}{{ doc.tax_id }}
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{% if doc.contact_person %}\n\t\t\t\t\t\n\t\t\t\t\t{% if doc.contact_phone %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_mobile %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% if doc.contact_email %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{#- Ship-to only when it differs from the billing address above. -#}\n\t\t\t\t\t{%- set shipping_address = get_print_address(doc.shipping_address_name) -%}\n\t\t\t\t\t{% if shipping_address and shipping_address != address %}\n\t\t\t\t\t\n\t\t\t\t\t{% endif %}\n\t\t\t\t\n\t\t\t
{{ _(doc.meta.get_label(\"contact_person\")) }}{{ doc.contact_display or doc.contact_person }}
{{ _(doc.meta.get_label(\"contact_phone\")) }}{{ doc.contact_phone }}
{{ _(doc.meta.get_label(\"contact_mobile\")) }}{{ doc.contact_mobile }}
{{ _(doc.meta.get_label(\"contact_email\")) }}{{ doc.contact_email }}
{{ _(\"Shipping Address\") }}{{ shipping_address }}
\n\t\t
\n\t
\n\t{% endif %}\n\n\t{% for section in page %}\n\t
\n\t\t{%- if doc.print_line_breaks and loop.index != 1 -%}
{%- endif -%}\n\t\t{%- if doc.print_section_headings and section.label and section.has_data -%}\n\t\t

{{ _(section.label) }}

\n\t\t{%- endif -%}\n\t\t{%- set no_of_cols = section.columns|len -%}\n\t\t{%- set section_fieldnames = [] -%}\n\t\t{%- for column in section.columns -%}\n\t\t\t{%- for df in column.fields -%}{%- set ignored = section_fieldnames.append(df.fieldname) -%}{%- endfor -%}\n\t\t{%- endfor -%}\n\t\t{%- if \"total\" in section_fieldnames -%}\n\t\t{#- \"total\" sits in a 3-column section, so its label/value grid does not line up with the\n\t\t Taxes and Totals sections below it. Render it in the same right-half 6/6 grid they use. -#}\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t{%- for column in section.columns -%}{{ render_fields(column.fields, 1) }}{%- endfor -%}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t{%- else -%}\n\t\t{% for column in section.columns %}\n\t\t\t
\n\t\t\t{%- if loop.first and \"in_words\" in section_fieldnames and doc.in_words -%}\n\t\t\t{#- The Totals section's left column is empty in print; put the amount in words there,\n\t\t\t on the same line as Grand Total, instead of under Rounded Total on the right. -#}\n\t\t\t\t
\n\t\t\t\t\t
{{ get_in_words(doc) }}
\n\t\t\t\t
\n\t\t\t{%- endif -%}\n\t\t\t{{ render_fields(column.fields, no_of_cols) }}\n\t\t\t
\n\t\t{% endfor %}\n\t\t{%- endif -%}\n\t
\n\t{% endfor %}\n\n\t{% if loop.last %}\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.customer_name or doc.customer }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Accepted By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"On behalf of\") }} {{ doc.company }}
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
{{ _(\"Approved By\") }}{{ _(\"Date\") }}
\n\t\t\t\t\t\t\t
{{ _(\"Position\") }}
\n\t\t\t\t\t\t
\n\t\t
\n\t
\n\t{% endif %}\n\n\t
\n\t\t{% if not no_letterhead and footer %}\n\t\t
\n\t\t\t{{ footer }}\n\t\t
\n\t\t{% endif %}\n\t\t{% if print_settings.repeat_header_footer %}\n\t\t\t

\n\t\t\t\t{{ _(\"Page {0} of {1}\").format('', '') }}\n\t\t\t

\n\t\t{% endif %}\n\t
\n
\n{% endfor %}\n", "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2026-09-23 12:00:00.000000", + "modified": "2026-10-20 12:00:00.000000", "module": "Default Thai Company", "name": "Default Standard Sales Order", "page_number": "Hide", diff --git a/default_thai_company/hooks.py b/default_thai_company/hooks.py index 924b800..7586649 100644 --- a/default_thai_company/hooks.py +++ b/default_thai_company/hooks.py @@ -81,6 +81,7 @@ jinja = { "methods": [ "default_thai_company.utils.get_letter_head_company", "default_thai_company.utils.get_company_bank_account", + "default_thai_company.utils.get_print_address", "default_thai_company.utils.get_in_words", "default_thai_company.tax_withholding.get_withholding_certificate", ], diff --git a/default_thai_company/tax_withholding.py b/default_thai_company/tax_withholding.py index 21828f1..0c72ab9 100644 --- a/default_thai_company/tax_withholding.py +++ b/default_thai_company/tax_withholding.py @@ -440,7 +440,10 @@ def pnd_form(row, supplier_type): def one_line_address(address_name): - """Address in the order of the Thailand Address Template, on one line.""" + """Address in the order of the Thailand Address Template, on one line. + + The form is Thai, so each part goes through its Thai translation (the + Address field's translate button) whatever the print language.""" if not address_name: return None address = frappe.get_cached_doc("Address", address_name) @@ -450,11 +453,13 @@ def one_line_address(address_name): address.county, address.city, address.state, - address.pincode, ] + parts = [_(part.strip(), lang="th") for part in parts if part and part.strip()] + if address.pincode and address.pincode.strip(): + parts.append(address.pincode.strip()) if address.country and address.country != "Thailand": - parts.append(address.country) - return " ".join(part.strip() for part in parts if part and part.strip()) + parts.append(_(address.country, lang="th")) + return " ".join(parts) def tax_id_digits(tax_id): diff --git a/default_thai_company/utils.py b/default_thai_company/utils.py index 627ae27..73726ce 100644 --- a/default_thai_company/utils.py +++ b/default_thai_company/utils.py @@ -53,6 +53,18 @@ def get_letter_head_company(doc=None): ) +def get_print_address(address_name): + """Address rendered in the print language for print formats. + + The `*_address_display` fields on a document are rendered once, when it is + saved, in the language of whoever saved it; re-render the linked Address + here so the Address Template's translations follow the print language. + """ + if not address_name: + return None + return render_address(address_name, check_permissions=False) + + def get_company_bank_account(company=None): """Default company Bank Account for print formats.