mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-17 20:02:38 +00:00
Compare commits
37 Commits
assets-dev
...
print-form
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e1531e6e1 | ||
|
|
255a5a271e | ||
|
|
14bdef18af | ||
|
|
8062ce686a | ||
|
|
7edc4828e0 | ||
|
|
76e679fb2b | ||
|
|
cb3d6a6d2f | ||
|
|
85479bc70a | ||
|
|
5360aebe12 | ||
|
|
2db327bf0c | ||
|
|
fe8e834b68 | ||
|
|
4aa31d6d58 | ||
|
|
c767929116 | ||
|
|
dbdf52b9cc | ||
|
|
fb1f216523 | ||
|
|
4f5c9ff36f | ||
|
|
10ff94ab2c | ||
|
|
24963273be | ||
|
|
e51e8be0a4 | ||
|
|
c51e1100cc | ||
|
|
2bf0f8705a | ||
|
|
acba13ec61 | ||
|
|
086df90290 | ||
|
|
156f09e1d9 | ||
|
|
b008e85697 | ||
|
|
6b5a72438a | ||
|
|
c1bf2c7080 | ||
|
|
b6d4d1c5c2 | ||
|
|
63d12b5ed9 | ||
|
|
6ee1774a94 | ||
|
|
e919d8bba5 | ||
|
|
1c563cebde | ||
|
|
a717739a0b | ||
|
|
f194e4c4f0 | ||
|
|
1753111ac2 | ||
|
|
d2dd440d59 | ||
|
|
59af8fada0 |
@@ -81,6 +81,7 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase):
|
||||
process_soa = create_process_soa(
|
||||
name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable"
|
||||
)
|
||||
|
||||
send_emails(process_soa.name, from_scheduler=True)
|
||||
process_soa.load_from_db()
|
||||
self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||
|
||||
@@ -279,6 +279,41 @@ class SalesInvoice(SellingController):
|
||||
self.indicator_color = "green"
|
||||
self.indicator_title = _("Paid")
|
||||
|
||||
def before_print(self, settings=None):
|
||||
from frappe.contacts.doctype.address.address import get_address_display_list
|
||||
|
||||
company_details = frappe.get_value(
|
||||
"Company", self.company, ["company_logo", "website", "phone_no", "email"], as_dict=True
|
||||
)
|
||||
|
||||
address_display_list = get_address_display_list("Company", self.company)
|
||||
address_line = address_display_list[0] if address_display_list else ""
|
||||
|
||||
required_fields = [
|
||||
company_details.get("company_logo"),
|
||||
company_details.get("website"),
|
||||
company_details.get("phone_no"),
|
||||
company_details.get("email"),
|
||||
self.company_address,
|
||||
address_line,
|
||||
]
|
||||
|
||||
if not all(required_fields):
|
||||
frappe.publish_realtime(
|
||||
"sales_invoice_before_print",
|
||||
{
|
||||
"company_logo": company_details.get("company_logo"),
|
||||
"website": company_details.get("website"),
|
||||
"phone_no": company_details.get("phone_no"),
|
||||
"email": company_details.get("email"),
|
||||
"address_line": address_line,
|
||||
"company": self.company,
|
||||
"company_address": self.company_address,
|
||||
"name": self.name,
|
||||
},
|
||||
user=frappe.session.user,
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_auto_set_posting_time()
|
||||
super().validate()
|
||||
@@ -2802,6 +2837,56 @@ def get_loyalty_programs(customer):
|
||||
return lp_details
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_company_master_details(name, company, details):
|
||||
from frappe.utils import validate_email_address
|
||||
|
||||
if isinstance(details, str):
|
||||
details = frappe.parse_json(details)
|
||||
|
||||
if details.get("email"):
|
||||
validate_email_address(details.email, throw=True)
|
||||
|
||||
company_fields = ["company_logo", "website", "phone_no", "email"]
|
||||
updated_fields = {field: details.get(field) for field in company_fields if details.get(field)}
|
||||
|
||||
if updated_fields:
|
||||
frappe.db.set_value("Company", company, updated_fields)
|
||||
|
||||
company_address = details.get("company_address")
|
||||
if details.get("address_line1"):
|
||||
address_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Address",
|
||||
"address_title": details.get("address_title"),
|
||||
"address_type": details.get("address_type"),
|
||||
"address_line1": details.get("address_line1"),
|
||||
"city": details.get("city"),
|
||||
"state": details.get("state"),
|
||||
"pincode": details.get("pincode"),
|
||||
"country": details.get("country"),
|
||||
"is_your_company_address": 1,
|
||||
"links": [{"link_doctype": "Company", "link_name": company}],
|
||||
}
|
||||
)
|
||||
address_doc.insert(ignore_permissions=True)
|
||||
company_address = address_doc.name
|
||||
|
||||
if company_address:
|
||||
current_display = frappe.db.get_value("Sales Invoice", name, "company_address_display")
|
||||
if not current_display or details.get("address_line1"):
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice",
|
||||
name,
|
||||
{
|
||||
"company_address": company_address,
|
||||
"company_address_display": get_address_display(company_address),
|
||||
},
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_invoice_discounting(source_name, target_doc=None):
|
||||
invoice = frappe.get_doc("Sales Invoice", source_name)
|
||||
|
||||
112
erpnext/accounts/letterhead/letterhead_plain.html
Normal file
112
erpnext/accounts/letterhead/letterhead_plain.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<style>
|
||||
.letter-head {
|
||||
border-radius: 18px;
|
||||
padding-right: 12px;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.letter-head td{
|
||||
padding: 0px !important;
|
||||
}
|
||||
.invoice-header {
|
||||
width: 100%;
|
||||
}
|
||||
.logo-cell {
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
|
||||
}
|
||||
.logo-container {
|
||||
width: 90px;
|
||||
border: 1px solid #EDEDED;
|
||||
border-radius: 15px;
|
||||
display: block;
|
||||
}
|
||||
.logo-container img {
|
||||
max-width: 90px;
|
||||
max-height: 90px;
|
||||
display: inline-block;
|
||||
border-radius: 15px;
|
||||
}
|
||||
.company-details {
|
||||
width: 40%;
|
||||
align-content: center;
|
||||
}
|
||||
.company-name {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #171717;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.invoice-info-cell {
|
||||
float: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
.invoice-info {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.invoice-label {
|
||||
color: #7C7C7C;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<table class="invoice-header">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="logo-cell" style="vertical-align: middle !important;">
|
||||
<div class="logo-container">
|
||||
{% set company_logo = frappe.db.get_value("Company", doc.company, "company_logo") %}
|
||||
{% if company_logo %}
|
||||
<img src="{{ frappe.utils.get_url(company_logo) }}" alt="Company Logo">
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="company-details">
|
||||
<div class="company-name">
|
||||
{{ doc.company }}
|
||||
</div>
|
||||
{% if doc.company_address %}
|
||||
{% set company_address_display = frappe.get_doc("Address", doc.company_address) %}
|
||||
{{ company_address_display.address_line1 or "" }}
|
||||
{% if company_address_display.address_line2 %}{{ company_address_display.address_line2 }}{% endif %}<br>
|
||||
{{ company_address_display.city or "" }} {{ company_address_display.state or "" }} {{ company_address_display.pincode or "" }} {{ company_address_display.country or "" }}<br>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="invoice-info-cell">
|
||||
{% set website = frappe.db.get_value("Company", doc.company, "website") %}
|
||||
{% set email = frappe.db.get_value("Company", doc.company, "email") %}
|
||||
{% set phone_no = frappe.db.get_value("Company", doc.company, "phone_no") %}
|
||||
|
||||
<div class="invoice-info">
|
||||
<span class="invoice-label">{{ _("Invoice:") }}</span>
|
||||
<span>{{ doc.name }}</span>
|
||||
</div>
|
||||
{% if website %}
|
||||
<div class="invoice-info">
|
||||
<span class="invoice-label">{{ _("Website:") }}</span>
|
||||
<span>{{ website }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if email %}
|
||||
<div class="invoice-info">
|
||||
<span class="invoice-label">{{ _("Email:") }}</span>
|
||||
<span>{{ email }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if phone_no %}
|
||||
<div class="invoice-info">
|
||||
<span class="invoice-label">{{ _("Contact:") }}</span>
|
||||
<span>{{ phone_no }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,125 @@
|
||||
<style>
|
||||
.print-format-preview {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.letter-head {
|
||||
border-radius: 18px;
|
||||
background: #f8f8f8;
|
||||
padding: 12px;
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.letterhead-container {
|
||||
width: 100%;
|
||||
}
|
||||
.letterhead-container .other-details {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.logo-address {
|
||||
width: 65%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 90px;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
border-radius: 15px;
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
color: #171717;
|
||||
font-weight: bold;
|
||||
line-height: 23px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.company-address {
|
||||
color: #171717;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.invoice-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.invoice-number {
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
color: #7c7c7c;
|
||||
width: 60px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.contact-value {
|
||||
color: #171717;
|
||||
display: inline-block;
|
||||
}
|
||||
.letterhead-container td {
|
||||
padding: 0px !important;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<table class="letterhead-container">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="logo-address">
|
||||
{% set company_logo = frappe.db.get_value("Company", doc.company, "company_logo") %} {% if
|
||||
company_logo %}
|
||||
<div class="logo">
|
||||
<img src="{{ frappe.utils.get_url(company_logo) }}" />
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="company-name">{{ doc.company }}</div>
|
||||
<div class="company-address">
|
||||
{% if doc.company_address %} {% set company_address_display = frappe.get_doc("Address",
|
||||
doc.company_address) %} {{ company_address_display.address_line1 or "" }} {% if
|
||||
company_address_display.address_line2 %}{{ company_address_display.address_line2 }}<br />{%
|
||||
endif %} {{ company_address_display.city or "" }} {{ company_address_display.state or ""
|
||||
}} {{ company_address_display.pincode or "" }} {{ company_address_display.country or ""
|
||||
}}<br />
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style="vertical-align: top">
|
||||
<div style="height: 90px; margin-bottom: 10px; text-align: right">
|
||||
<div class="invoice-title">{{ _("Sales Invoice") }}</div>
|
||||
<div class="invoice-number">{{ doc.name }}</div>
|
||||
<br />
|
||||
</div>
|
||||
<div style="text-align: left; float: right" class="other-details">
|
||||
{% set website = frappe.db.get_value("Company", doc.company, "website") %} {% set email =
|
||||
frappe.db.get_value("Company", doc.company, "email") %} {% set phone_no =
|
||||
frappe.db.get_value("Company", doc.company, "phone_no") %} {% if website %}
|
||||
<div>
|
||||
<span class="contact-title">{{ _("Website:") }}</span
|
||||
><span class="contact-value">{{ website }}</span>
|
||||
</div>
|
||||
{% endif %} {% if email %}
|
||||
<div>
|
||||
<span class="contact-title">{{ _("Email:") }}</span
|
||||
><span class="contact-value">{{ email }}</span>
|
||||
</div>
|
||||
{% endif %} {% if phone_no %}
|
||||
<div>
|
||||
<span class="contact-title">{{ _("Contact:") }}</span
|
||||
><span class="contact-value">{{ phone_no }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,332 @@
|
||||
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None,
|
||||
print_heading_template=None) -%}
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
{% if print_heading_template %}
|
||||
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
|
||||
{% endif %}
|
||||
{%- endmacro -%}
|
||||
{% for page in layout %}
|
||||
<div class="page-break invoice-page">
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.letter-head {
|
||||
padding-top: 12px !important;
|
||||
padding-bottom: 12px !important;
|
||||
}
|
||||
.invoice-page {
|
||||
font-family: Inter, sans-serif;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.print-format-body {
|
||||
padding: 30px 12px 12px 12px !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #7c7c7c !important;
|
||||
}
|
||||
.text-muted {
|
||||
color: #7c7c7c !important;
|
||||
}
|
||||
.text-dark {
|
||||
color: #171717 !important;
|
||||
}
|
||||
.small-text {
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.text-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mt-15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.mt-40 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.mw-400 {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.items-table,
|
||||
.tax-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.items-table td,
|
||||
.tax-table td {
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.items-table thead td,
|
||||
.tax-table thead td {
|
||||
color: #7c7c7c !important;
|
||||
}
|
||||
.items-table thead tr:first-of-type td,
|
||||
.tax-table thead tr:first-of-type td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.items-table thead tr:first-child td:first-child,
|
||||
.tax-table thead tr:first-child td:first-child {
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
.items-table thead tr:first-child td:last-child,
|
||||
.tax-table thead tr:first-child td:last-child {
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
.in-words {
|
||||
color: #171717 !important;
|
||||
max-width: 400px;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.row-divider {
|
||||
border-bottom: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
.highlight-bg {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin-top: 40px;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.print-format {
|
||||
color: #171717;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 420;
|
||||
line-height: 21px;
|
||||
padding: 0px !important;
|
||||
margin-left: 0mm !important;
|
||||
margin-right: 0mm !important;
|
||||
}
|
||||
|
||||
.letter-head-footer {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.print-format-body td {
|
||||
padding: 8px 8px !important;
|
||||
}
|
||||
|
||||
.tax-table td,
|
||||
.items-table td {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.tax-table thead td,
|
||||
.items-table thead td {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.item-img td {
|
||||
border: none !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="print-format-body">
|
||||
<table class="info-table col-xs-12" style="width: 100%; margin-left: 12px">
|
||||
<tr>
|
||||
<td class="col-xs-6" style="padding: 0 !important">
|
||||
<div class="title col-xs-5 p-0">
|
||||
<div class="info-text">Customer Name:</div>
|
||||
<div class="info-text">Bill to:</div>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="info-text">{{ doc.customer_name }}</div>
|
||||
<div class="info-text">
|
||||
{% if doc.customer_address %}
|
||||
{% set customer_address = frappe.get_doc("Address", doc.customer_address) %}
|
||||
{{ customer_address.address_line1 or "" }}<br>
|
||||
{% if customer_address.address_line2 %}{{ customer_address.address_line2 }}<br>{% endif %}
|
||||
{{ customer_address.city or "" }} {{ customer_address.state or "" }} {{ customer_address.pincode or "" }} {{ customer_address.country or "" }}<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="col-xs-6" style="padding: 0 !important">
|
||||
<div class="title col-xs-5 p-0">
|
||||
<div class="info-text">Invoice Number:</div>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="info-text">{{ doc.name }}</div>
|
||||
</div>
|
||||
<div class="title col-xs-5 p-0">
|
||||
<div class="info-text">Invoice Date:</div>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="info-text">{{ frappe.utils.format_date(doc.posting_date) }}</div>
|
||||
</div>
|
||||
<div class="title col-xs-5 p-0">
|
||||
<div class="info-text">Payment Due Date:</div>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
<div class="info-text">{{ frappe.utils.format_date(doc.due_date) }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Items Table -->
|
||||
<table class="items-table mt-15">
|
||||
<colgroup>
|
||||
<col style="width: 5%" />
|
||||
<col style="width: 35%" />
|
||||
<col style="width: 10%" />
|
||||
<col style="width: 20%" />
|
||||
<col style="width: 15%" />
|
||||
</colgroup>
|
||||
<thead class="highlight-bg">
|
||||
<tr>
|
||||
<td class="text-center">{{ _("No") }}</td>
|
||||
<td class="text-left">{{ _("Item") }}</td>
|
||||
<td class="text-right">{{ _("Quantity") }}</td>
|
||||
<td class="text-right">{{ _("Rate") }}</td>
|
||||
<td class="text-right">{{ _("Amount") }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in doc.items %}
|
||||
<tr>
|
||||
<td class="text-center">{{ loop.index }}</td>
|
||||
<td class="text-left">
|
||||
<table class="item-img" style="border-collapse: collapse">
|
||||
<tr>
|
||||
{% if item.image %}
|
||||
<td class="no-style" style="border-radius: 6px; vertical-align: middle">
|
||||
<img
|
||||
src="{{ item.image }}"
|
||||
alt="{{ item.item_name }}"
|
||||
style="
|
||||
border-radius: 6px;
|
||||
max-height: 24px;
|
||||
min-height: 24px;
|
||||
max-width: 24px;
|
||||
min-width: 24px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td
|
||||
class="no-style"
|
||||
style="vertical-align: middle !important; padding: 0 !important"
|
||||
>
|
||||
{{ item.item_name }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
<td class="text-right">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
|
||||
<td class="text-right">{{ item.get_formatted("net_rate", doc) }}</td>
|
||||
<td class="text-right">{{ item.get_formatted("net_amount", doc) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="test" style="float: right; margin-top: 15px; margin-bottom: 5px;">
|
||||
<table style="width: 100%; border-collapse: collapse">
|
||||
<tr class="row-divider">
|
||||
<td class="text-right text-muted" style="padding-right: 30px !important;">{{ _("Sub Total:") }}</td>
|
||||
<td class="text-right">{{ doc.get_formatted("total", doc) }}</td>
|
||||
</tr>
|
||||
|
||||
{%- if doc.apply_discount_on == "Net Total" -%}
|
||||
<tr class="row-divider">
|
||||
<td class="text-right text-muted" style="padding-right: 30px !important;">
|
||||
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
|
||||
</td>
|
||||
<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
{%- for tax in doc.taxes -%}
|
||||
{%- if (tax.tax_amount or print_settings.print_taxes_with_zero_amount) and (not tax.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||
<tr class="row-divider">
|
||||
<td class="text-right text-muted" style="padding-right: 30px !important;">{{ tax.get_formatted("description") }} ({{ tax.get_formatted("rate") }}%):</td>
|
||||
<td class="text-right">{{ tax.get_formatted("tax_amount") }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- if doc.apply_discount_on == "Grand Total" -%}
|
||||
<tr class="row-divider">
|
||||
<td class="text-right text-muted" style="padding-right: 30px !important;">
|
||||
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
|
||||
</td>
|
||||
<td class="text-right">{{ doc.get_formatted("discount_amount", doc) }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="border-bottom: 1px solid #ededed;">
|
||||
<table class="highlight-bg" style="width: 100%; border-collapse: collapse; border-radius: 12px; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td class="text-left mw-400">
|
||||
<div class="text-capitalize">
|
||||
<span class="title"> {{ _("In Words: ") }}</span>{{ doc.in_words }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right"><b>{{ _("Grand Total:") }}</b></td>
|
||||
<td class="text-right">
|
||||
<span style="font-weight: 700">
|
||||
{{ doc.get_formatted("grand_total", doc) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Terms -->
|
||||
{% if doc.terms %}
|
||||
<div class="info-card">
|
||||
<div class="title">{{ _("Terms and Conditions") }}</div>
|
||||
{{ doc.terms}}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"absolute_value": 0,
|
||||
"align_labels_right": 0,
|
||||
"creation": "2025-09-15 16:31:00.732539",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en",
|
||||
"disabled": 0,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font_size": 14,
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"margin_bottom": 15.0,
|
||||
"margin_left": 15.0,
|
||||
"margin_right": 15.0,
|
||||
"margin_top": 15.0,
|
||||
"modified": "2025-09-15 16:31:00.732539",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Print Format Sales Invoice",
|
||||
"owner": "Administrator",
|
||||
"page_number": "Hide",
|
||||
"pdf_generator": "wkhtmltopdf",
|
||||
"print_format_builder": 0,
|
||||
"print_format_builder_beta": 0,
|
||||
"print_format_for": "DocType",
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 0,
|
||||
"standard": "Yes"
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None,
|
||||
print_heading_template=None) -%}
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
{% if print_heading_template %}
|
||||
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
|
||||
<div class="text-center" document-status="cancelled">
|
||||
<h4 style="margin: 0px">{{ _("CANCELLED") }}</h4>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endmacro -%} {% for page in layout %}
|
||||
<div class="page-break invoice-wrapper">
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.letter-head {
|
||||
margin-top: 12px !important;
|
||||
}
|
||||
.invoice-wrapper {
|
||||
font-family: "Inter", sans-serif;
|
||||
color: var(--black-overlay-700);
|
||||
}
|
||||
|
||||
.print-format-body {
|
||||
padding: 30px 12px 12px 12px !important;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 0mm !important;
|
||||
padding: 0px !important;
|
||||
}
|
||||
|
||||
table.info-table,
|
||||
table.items-table,
|
||||
table.tax-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border: 1px solid #ededed;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table.info-table td {
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 8px 10px !important;
|
||||
}
|
||||
table.items-table td,
|
||||
table.tax-table td {
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
table.info-table td:not(:first-of-type),
|
||||
table.items-table td:not(:first-of-type),
|
||||
table.tax-table td:not(:first-of-type) {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
table.info-table tbody tr:last-of-type td,
|
||||
table.items-table tbody tr:last-of-type td,
|
||||
table.tax-table tbody tr:last-of-type td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
thead.table-header {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
thead.table-header td {
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.text-muted {
|
||||
color: #7c7c7c;
|
||||
}
|
||||
.text-dark {
|
||||
color: #525252;
|
||||
}
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.small-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.mt-15 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.mt-40 {
|
||||
margin-top: 40px;
|
||||
}
|
||||
.mt-80 {
|
||||
margin-top: 80px;
|
||||
}
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.pb-8 {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.amount-width {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.totals-table {
|
||||
border-radius: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
.totals-table td {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
.totals-table tr {
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
.totals-table tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.totals-table .grand-total td {
|
||||
padding: 6px 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.words-box {
|
||||
background: #f8f8f8;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
line-height: 21px;
|
||||
letter-spacing: 0.21px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #7c7c7c !important;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: #525252 !important;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.print-format {
|
||||
color: #171717;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 420;
|
||||
line-height: 21px;
|
||||
padding: 0px;
|
||||
letter-spacing: 0.14px;
|
||||
margin-left: 0mm !important;
|
||||
margin-right: 0mm !important;
|
||||
}
|
||||
.letter-head-footer {
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
.print-heading {
|
||||
margin-top: 0px !important;
|
||||
margin-bottom: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<div class="print-format-body">
|
||||
<table class="info-table mb-20" style="width: 100%">
|
||||
<tr>
|
||||
<td style="width: 50%">
|
||||
<span class="heading">{{ _("Customer Name") }}:</span> {{doc.customer_name }}
|
||||
</td>
|
||||
<td style="width: 50%">
|
||||
<span class="heading">{{ _("Payment Due Date") }}:</span> {{
|
||||
frappe.utils.format_date(doc.due_date) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="heading">{{ _("Invoice Number") }}:</span> {{ doc.name }}</td>
|
||||
<td>
|
||||
<span class="heading">{{ _("Invoice Date") }}:</span> {{
|
||||
frappe.utils.format_date(doc.posting_date) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="heading">{{ _("Bill From") }}:</span><br />
|
||||
{% if doc.customer_address %}
|
||||
{% set customer_address = frappe.get_doc("Address", doc.customer_address) %}
|
||||
{{ doc.customer_name }}<br>
|
||||
{{ customer_address.address_line1 or "" }}
|
||||
{% if customer_address.address_line2 %}{{ customer_address.address_line2 }}{% endif %}<br>
|
||||
{{ customer_address.city or "" }} {{ customer_address.state or "" }} {{ customer_address.pincode or "" }} {{ customer_address.country or "" }}<br>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="heading">{{ _("Bill To") }}:</span><br />
|
||||
{% if doc.company_address %}
|
||||
{% set company_address_display = frappe.get_doc("Address", doc.company_address) %}
|
||||
{{ doc.company }}<br>
|
||||
{{ company_address_display.address_line1 or "" }}
|
||||
{% if company_address_display.address_line2 %}{{ company_address_display.address_line2 }}{% endif %}<br>
|
||||
{{ company_address_display.city or "" }} {{ company_address_display.state or "" }} {{ company_address_display.pincode or "" }} {{ company_address_display.country or "" }}<br>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Items Table -->
|
||||
<table class="items-table mt-15" style="width: 100%">
|
||||
<colgroup>
|
||||
<col style="width: 5%" />
|
||||
<col style="width: 40%" />
|
||||
<col style="width: 15%" />
|
||||
<col style="width: 20%" />
|
||||
<col style="width: 20%" />
|
||||
</colgroup>
|
||||
<thead class="table-header">
|
||||
<tr>
|
||||
<td class="text-center">{{ _("No") }}</td>
|
||||
<td class="text-left">{{ _("Item") }}</td>
|
||||
<td class="text-right">{{ _("Quantity") }}</td>
|
||||
<td class="text-right">{{ _("Rate") }}</td>
|
||||
<td class="text-right">{{ _("Amount") }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in doc.items %}
|
||||
<tr>
|
||||
<td class="text-center text-dark">{{ loop.index }}</td>
|
||||
<td>{{ item.item_name }}</td>
|
||||
<td class="text-right text-dark">{{ item.get_formatted("qty", 0) }} {{ item.uom }}</td>
|
||||
<td class="text-right text-dark">{{ item.get_formatted("net_rate", doc) }}</td>
|
||||
<td class="text-right" style="color: #171717">
|
||||
{{ item.get_formatted("net_amount", doc) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Totals Section -->
|
||||
<table style="width:100%; margin-top: 15px">
|
||||
<td style="vertical-align: bottom !important;">
|
||||
<p class="title">{{ _("Total in words") }}</p>
|
||||
<div class="words-box text-uppercase">{{ doc.in_words }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<table class="totals-table">
|
||||
<tr>
|
||||
<td class="text-right text-muted">{{ _("Sub Total:") }}</td>
|
||||
<td class="text-right amount-width">{{ doc.get_formatted("total", doc) }}</td>
|
||||
</tr>
|
||||
{%- if doc.apply_discount_on == "Net Total" -%}
|
||||
<tr>
|
||||
<td class="text-right text-muted">
|
||||
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
|
||||
</td>
|
||||
<td class="text-right amount-width">{{ doc.get_formatted("discount_amount", doc) }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
{%- for tax in doc.taxes -%}
|
||||
{%- if (tax.tax_amount or print_settings.print_taxes_with_zero_amount) and (not tax.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||
<tr>
|
||||
<td class="text-right text-muted">{{ tax.get_formatted("description") }} ({{ tax.get_formatted("rate") }}%):</td>
|
||||
<td class="text-right amount-width">{{ tax.get_formatted("tax_amount") }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- if doc.apply_discount_on == "Grand Total" -%}
|
||||
<tr>
|
||||
<td class="text-right text-muted">
|
||||
{{ _("Discount") }} ({{ doc.additional_discount_percentage }}%):
|
||||
</td>
|
||||
<td class="text-right amount-width">{{ doc.get_formatted("discount_amount", doc) }}</td>
|
||||
</tr>
|
||||
{%- endif -%}
|
||||
<tr class="grand-total">
|
||||
<td class="text-right">{{ _("Grand Total:") }}</td>
|
||||
<td class="text-right amount-width">{{ doc.get_formatted("grand_total", doc) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</table>
|
||||
|
||||
<!-- Terms -->
|
||||
<div class="terms-section">
|
||||
{% if doc.terms %}
|
||||
<div class="info-card mt-40">
|
||||
<div>{{ _("Terms and Conditions") }}</div>
|
||||
{{ doc.terms}}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"absolute_value": 0,
|
||||
"align_labels_right": 0,
|
||||
"creation": "2025-08-28 04:03:36.284420",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en",
|
||||
"disabled": 0,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font_size": 14,
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"margin_bottom": 15.0,
|
||||
"margin_left": 15.0,
|
||||
"margin_right": 15.0,
|
||||
"margin_top": 15.0,
|
||||
"modified": "2025-09-01 17:47:14.710435",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Print Format",
|
||||
"owner": "Administrator",
|
||||
"page_number": "Hide",
|
||||
"pdf_generator": "wkhtmltopdf",
|
||||
"print_format_builder": 0,
|
||||
"print_format_builder_beta": 0,
|
||||
"print_format_for": "DocType",
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 0,
|
||||
"standard": "Yes"
|
||||
}
|
||||
@@ -51,6 +51,8 @@ doctype_list_js = {
|
||||
],
|
||||
}
|
||||
|
||||
page_js = {"print": "public/js/print.js"}
|
||||
|
||||
extend_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
|
||||
|
||||
override_whitelisted_methods = {"frappe.www.contact.send_message": "erpnext.templates.utils.send_message"}
|
||||
@@ -600,6 +602,7 @@ user_privacy_documents = [
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# ERPNext doctypes for Global Search
|
||||
global_search_doctypes = {
|
||||
"Default": [
|
||||
|
||||
139
erpnext/public/js/print.js
Normal file
139
erpnext/public/js/print.js
Normal file
@@ -0,0 +1,139 @@
|
||||
let beforePrintHandled = false;
|
||||
|
||||
frappe.realtime.on("sales_invoice_before_print", (data) => {
|
||||
const route = frappe.get_route();
|
||||
|
||||
if (!beforePrintHandled && route[0] === "print" && route[1] === "Sales Invoice") {
|
||||
beforePrintHandled = true;
|
||||
|
||||
let companyDetailsDialog = new frappe.ui.Dialog({
|
||||
title: "Enter Company Details",
|
||||
fields: [
|
||||
{
|
||||
label: "Company Logo",
|
||||
fieldname: "company_logo",
|
||||
fieldtype: "Attach Image",
|
||||
reqd: data.company_logo ? 0 : 1,
|
||||
hidden: data.company_logo ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Website",
|
||||
fieldname: "website",
|
||||
fieldtype: "Data",
|
||||
reqd: data.website ? 0 : 1,
|
||||
hidden: data.website ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Phone No",
|
||||
fieldname: "phone_no",
|
||||
fieldtype: "Data",
|
||||
reqd: data.phone_no ? 0 : 1,
|
||||
hidden: data.phone_no ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Email",
|
||||
fieldname: "email",
|
||||
fieldtype: "Data",
|
||||
options: "Email",
|
||||
reqd: data.email ? 0 : 1,
|
||||
hidden: data.email ? 1 : 0,
|
||||
},
|
||||
{
|
||||
fieldname: "section_break_1",
|
||||
fieldtype: "Section Break",
|
||||
},
|
||||
{
|
||||
label: "Address Title",
|
||||
fieldname: "address_title",
|
||||
fieldtype: "Data",
|
||||
reqd: data.address_line ? 0 : 1,
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Address Type",
|
||||
fieldname: "address_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Billing", "Shipping"],
|
||||
default: "Billing",
|
||||
reqd: data.address_line ? 0 : 1,
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Address Line",
|
||||
fieldname: "address_line1",
|
||||
fieldtype: "Data",
|
||||
reqd: data.address_line ? 0 : 1,
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "City",
|
||||
fieldname: "city",
|
||||
fieldtype: "Data",
|
||||
reqd: data.address_line ? 0 : 1,
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "State",
|
||||
fieldname: "state",
|
||||
fieldtype: "Data",
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Country",
|
||||
fieldname: "country",
|
||||
fieldtype: "Link",
|
||||
options: "Country",
|
||||
reqd: data.address_line ? 0 : 1,
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Postal Code",
|
||||
fieldname: "pincode",
|
||||
fieldtype: "Data",
|
||||
hidden: data.address_line ? 1 : 0,
|
||||
},
|
||||
{
|
||||
label: "Select Company Address",
|
||||
fieldname: "company_address",
|
||||
fieldtype: "Link",
|
||||
options: "Address",
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
link_doctype: "Company",
|
||||
link_name: data.company,
|
||||
},
|
||||
};
|
||||
},
|
||||
reqd: data.address_line && !data.company_address ? 1 : 0,
|
||||
hidden: data.address_line && !data.company_address ? 0 : 1,
|
||||
},
|
||||
],
|
||||
primary_action_label: "Save",
|
||||
primary_action(values) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.save_company_master_details",
|
||||
args: {
|
||||
name: data.name,
|
||||
company: data.company,
|
||||
details: values,
|
||||
},
|
||||
callback: function () {
|
||||
companyDetailsDialog.hide();
|
||||
frappe.msgprint(__("Updating details."));
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
companyDetailsDialog.show();
|
||||
}
|
||||
});
|
||||
frappe.router.on("change", () => {
|
||||
const route = frappe.get_route();
|
||||
if (route[0] !== "print" || route[1] !== "Sales Invoice") {
|
||||
beforePrintHandled = false;
|
||||
}
|
||||
});
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import os
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
@@ -34,6 +36,7 @@ def after_install():
|
||||
update_roles()
|
||||
make_default_operations()
|
||||
update_pegged_currencies()
|
||||
create_letter_head()
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@@ -279,6 +282,28 @@ def update_pegged_currencies():
|
||||
doc.save()
|
||||
|
||||
|
||||
def create_letter_head():
|
||||
base_path = frappe.get_app_path("erpnext", "accounts", "letterhead")
|
||||
|
||||
letterheads = {
|
||||
"Letterhead with background colour": "letterhead_with_background_colour.html",
|
||||
"Letterhead Plain": "letterhead_plain.html",
|
||||
}
|
||||
|
||||
for name, filename in letterheads.items():
|
||||
if not frappe.db.exists("Letter Head", name):
|
||||
content = frappe.read_file(os.path.join(base_path, filename))
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Letter Head",
|
||||
"letter_head_name": name,
|
||||
"source": "HTML",
|
||||
"content": content,
|
||||
}
|
||||
)
|
||||
doc.insert(ignore_permissions=True)
|
||||
|
||||
|
||||
DEFAULT_ROLE_PROFILES = {
|
||||
"Inventory": [
|
||||
"Stock User",
|
||||
|
||||
Reference in New Issue
Block a user