Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e852383
feat: default print format for sales invoice
khushi8112 Sep 9, 2025
af974fb
feat: add css
khushi8112 Sep 9, 2025
156dda8
style: change padding
khushi8112 Sep 9, 2025
7386270
feat: add letterhead fixture
khushi8112 Sep 9, 2025
1adbf90
fix: css changes in letterhead
khushi8112 Sep 9, 2025
2bc1978
fix: letterhead styling
khushi8112 Sep 10, 2025
b5c739d
fix: broken img tag in letterhead
khushi8112 Sep 10, 2025
ddf4a83
fix: condition based address display
khushi8112 Sep 15, 2025
c780796
fix: remove border if company logo not available
khushi8112 Sep 15, 2025
4cc2afb
feat: print format design two
khushi8112 Sep 15, 2025
e08f829
fix: radius of the items/tax table thead
khushi8112 Sep 15, 2025
6b83309
test: just debugging
khushi8112 Sep 16, 2025
17397ae
test: add in_install condition for debugging
khushi8112 Sep 16, 2025
f1a2e1b
refactor: remove img tag for testing
khushi8112 Sep 16, 2025
5f97bec
refactor: revert debugging changes
khushi8112 Sep 16, 2025
5c4f778
style: center-align logo within its container div
khushi8112 Sep 17, 2025
f4f2d11
style: always show border even when logo is missing
khushi8112 Sep 17, 2025
6703610
fix: do not make letterhead default
khushi8112 Sep 17, 2025
39b6aab
fix: update styles to work with wkhtmltopdf rendering
khushi8112 Sep 18, 2025
e223731
refactor: remove flex usage for better wkhtmltopdf support
khushi8112 Sep 19, 2025
ce19514
refactor: update letterhead styles for wkhtmltopdf compatibility
khushi8112 Sep 19, 2025
a6d92e5
refactor: small changes for better readability
khushi8112 Sep 22, 2025
842a364
refactor: remove tax breakup table from the print format
khushi8112 Sep 22, 2025
f6ebf2d
feat: letterhead for print format
khushi8112 Sep 23, 2025
0d58dfd
feat: add default letterhead with HTML template via after_install
khushi8112 Sep 25, 2025
3abdfcb
fix: app path correctly
khushi8112 Sep 25, 2025
6494fc4
refactor: create_letter_head for readability
khushi8112 Sep 25, 2025
dbf9faa
feat: prompt user to input company logo and address if missing in pri…
khushi8112 Sep 29, 2025
780d3f5
fix: better sub total section with tax breakup
khushi8112 Sep 29, 2025
bf6c331
fix: show tax breakup in print format
khushi8112 Sep 30, 2025
301b294
style: fix layout issues with extended data
khushi8112 Sep 30, 2025
8a19dc4
style: format and display the address for improved visual clarity
khushi8112 Sep 30, 2025
abc7bf2
style: add company and customer name on bill_to and bill_from section
khushi8112 Oct 1, 2025
98838b1
feat: input website, email, phone_no if not already set in company
khushi8112 Oct 1, 2025
e3ca318
fix: small ui changes
khushi8112 Oct 1, 2025
610dcbb
chore: remove frappe.db.commit
khushi8112 Oct 1, 2025
92f69ae
fix: validate email address
khushi8112 Oct 7, 2025
5902074
style: slight spacing and alignment fix
khushi8112 Oct 9, 2025
50eb678
feat: condition based item code column
khushi8112 Oct 9, 2025
12ebab1
refactor: change print format type html to custom
khushi8112 Oct 10, 2025
3311095
refactor: replace get_doc with get_value
khushi8112 Oct 10, 2025
533257c
refactor: use get_value to improve performance
khushi8112 Oct 10, 2025
a4fe0fb
refactor: use query builder to set company address
khushi8112 Oct 11, 2025
6e07aac
fix: add filter query for address field
khushi8112 Oct 12, 2025
f14b3ed
refactor: add permission check and minor fixes
khushi8112 Oct 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_auto_email_for_process_soa_ar(self):
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)))
Expand Down
106 changes: 106 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,59 @@ def set_indicator(self):
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

super().before_print(settings)

company_details = frappe.get_value(
"Company", self.company, ["company_logo", "website", "phone_no", "email"], as_dict=True
)

required_fields = [
company_details.get("company_logo"),
company_details.get("phone_no"),
company_details.get("email"),
]

if not all(required_fields) and not frappe.has_permission("Company", "write", throw=False):
frappe.msgprint(
_(
"Some required Company details are missing. You don't have permission to update them. Please contact your System Manager."
)
)
return

if not self.company_address and not frappe.has_permission("Sales Invoice", "write", throw=False):
frappe.msgprint(
_(
"Company Address is missing. You don't have permission to update it. Please contact your System Manager."
)
)
return

address_display_list = get_address_display_list("Company", self.company)
address_line = address_display_list[0].get("address_line1") if address_display_list else ""

required_fields.append(self.company_address)
required_fields.append(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()
Expand Down Expand Up @@ -2802,6 +2855,59 @@ 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.get("email"), throw=True)

company_fields = ["company_logo", "website", "phone_no", "email"]
company_fields_to_update = {field: details.get(field) for field in company_fields if details.get(field)}

if company_fields_to_update:
frappe.db.set_value("Company", company, company_fields_to_update)

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"),
"address_line2": details.get("address_line2"),
"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()
company_address = address_doc.name

if company_address:
company_address_display = frappe.db.get_value("Sales Invoice", name, "company_address_display")
if not company_address_display or details.get("address_line1"):
from frappe.query_builder import DocType

SalesInvoice = DocType("Sales Invoice")

(
frappe.qb.update(SalesInvoice)
.set(SalesInvoice.company_address, company_address)
.set(SalesInvoice.company_address_display, get_address_display(company_address))
.where(SalesInvoice.name == name)
).run()

return True


@frappe.whitelist()
def create_invoice_discounting(source_name, target_doc=None):
invoice = frappe.get_doc("Sales Invoice", source_name)
Expand Down
108 changes: 108 additions & 0 deletions erpnext/accounts/letterhead/company_letterhead.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<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;
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 = frappe.db.get_value("Address", doc.company_address, ["address_line1", "address_line2", "city", "state", "pincode", "country"], as_dict=True) %}

{{ company_address.get("address_line1") or "" }}<br>
{% if company_address.get("address_line2") %}{{ company_address.get("address_line2") }}<br>{% endif %}
{{ company_address.get("city") or "" }}, {{ company_address.get("state") or "" }} {{ company_address.get("pincode") or "" }}, {{ company_address.get("country") or "" }}<br>
{% endif %}
</td>

<td class="invoice-info-cell">
{% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}

<div class="invoice-info">
<span class="invoice-label">{{ _("Invoice:") }}</span>
<span>{{ doc.name }}</span>
</div>
{% if company_details.website %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Website:") }}</span>
<span>{{ company_details.website }}</span>
</div>
{% endif %}
{% if company_details.email %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Email:") }}</span>
<span>{{ company_details.email }}</span>
</div>
{% endif %}
{% if company_details.phone_no %}
<div class="invoice-info">
<span class="invoice-label">{{ _("Contact:") }}</span>
<span>{{ company_details.phone_no }}</span>
</div>
{% endif %}
</td>
</tr>
</tbody>
</table>
125 changes: 125 additions & 0 deletions erpnext/accounts/letterhead/company_letterhead_grey.html
Original file line number Diff line number Diff line change
@@ -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;
}

.letter-head .logo {
width: 90px;
display: block;
margin-bottom: 10px;
}

.letter-head .logo img {
border-radius: 15px;
}

.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 = frappe.db.get_value("Address", doc.company_address, ["address_line1", "address_line2", "city", "state", "pincode", "country"], as_dict=True) %}
{{ company_address.address_line1 or "" }}<br />
{% if company_address.address_line2 %} {{ company_address.address_line2 }}<br /> {% endif %}
{{ company_address.city or "" }}, {{ company_address.state or "" }}
{{ company_address.pincode or "" }}, {{ company_address.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 company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
{% if company_details.website %}
<div>
<span class="contact-title">{{ _("Website:") }}</span
><span class="contact-value">{{ company_details.website }}</span>
</div>
{% endif %}
{% if company_details.email %}
<div>
<span class="contact-title">{{ _("Email:") }}</span
><span class="contact-value">{{ company_details.email }}</span>
</div>
{% endif %}
{% if company_details.phone_no %}
<div>
<span class="contact-title">{{ _("Contact:") }}</span
><span class="contact-value">{{ company_details.phone_no }}</span>
</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
Empty file.
Loading