diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py index 4f9dd9a590dc..7d5cfb90af85 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py @@ -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))) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 929db4895ad3..0b312cd6ac21 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -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() @@ -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) diff --git a/erpnext/accounts/letterhead/company_letterhead.html b/erpnext/accounts/letterhead/company_letterhead.html new file mode 100644 index 000000000000..f4b8db863f6d --- /dev/null +++ b/erpnext/accounts/letterhead/company_letterhead.html @@ -0,0 +1,108 @@ + + +
|
+
+ {% set company_logo = frappe.db.get_value("Company", doc.company, "company_logo") %}
+ {% if company_logo %}
+
+ |
+
+
+
+ {{ doc.company }}
+
+ {% 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 "" }}+ {% if company_address.get("address_line2") %}{{ company_address.get("address_line2") }} {% endif %} + {{ company_address.get("city") or "" }}, {{ company_address.get("state") or "" }} {{ company_address.get("pincode") or "" }}, {{ company_address.get("country") or "" }} + {% endif %} + |
+
+
+ {% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
+
+
+ {{ _("Invoice:") }}
+ {{ doc.name }}
+
+ {% if company_details.website %}
+
+ {{ _("Website:") }}
+ {{ company_details.website }}
+
+ {% endif %}
+ {% if company_details.email %}
+
+ {{ _("Email:") }}
+ {{ company_details.email }}
+
+ {% endif %}
+ {% if company_details.phone_no %}
+
+ {{ _("Contact:") }}
+ {{ company_details.phone_no }}
+
+ {% endif %}
+ |
+
|
+ {% set company_logo = frappe.db.get_value("Company", doc.company, "company_logo") %} {% if
+ company_logo %}
+
+
+ {% endif %}
+ {{ doc.company }}
+
+ {% 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 "" }}
+ + {% if company_address.address_line2 %} {{ company_address.address_line2 }} {% endif %} + {{ company_address.city or "" }}, {{ company_address.state or "" }} + {{ company_address.pincode or "" }}, {{ company_address.country or ""}} + {% endif %} + |
+
+
+
+
+ {{ _("Sales Invoice") }}
+ {{ doc.name }}
+ +
+ {% set company_details = frappe.db.get_value("Company", doc.company, ["website", "email", "phone_no"], as_dict=True) %}
+ {% if company_details.website %}
+
+
+ {{ _("Website:") }}{{ company_details.website }}
+
+ {% endif %}
+ {% if company_details.email %}
+
+ {{ _("Email:") }}{{ company_details.email }}
+
+ {% endif %}
+ {% if company_details.phone_no %}
+
+ {{ _("Contact:") }}{{ company_details.phone_no }}
+
+ {% endif %}
+ |
+
| \n\t\t\t\t\t{{ _(\"Customer Name\") }}: {{doc.customer_name }}\n\t\t\t\t | \n\t\t\t\t\n\t\t\t\t\t{{ _(\"Payment Due Date\") }}: {{\n\t\t\t\t\tfrappe.utils.format_date(doc.due_date) }}\n\t\t\t\t | \n\t\t\t
| {{ _(\"Invoice Number\") }}: {{ doc.name }} | \n\t\t\t\t\n\t\t\t\t\t{{ _(\"Invoice Date\") }}: {{\n\t\t\t\t\tfrappe.utils.format_date(doc.posting_date) }}\n\t\t\t\t | \n\t\t\t
| {{ _(\"Bill From\") }}: \n\t\t\t\t\t{% if doc.company_address %}\n {% set company_address = frappe.db.get_value(\"Address\", doc.company_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.company }} \n {{ company_address.get(\"address_line1\") or \"\" }} \n {% if company_address.get(\"address_line2\") %}{{ company_address.get(\"address_line2\") }} {% endif %}\n {{ company_address.get(\"city\") or \"\" }}, {{ company_address.get(\"state\") or \"\" }} {{ company_address.get(\"pincode\") or \"\" }}, {{ company_address.get(\"country\") or \"\" }} \n {% endif %}\n\t\t\t\t | \n\t\t\t\t{{ _(\"Bill To\") }}: \n\t\t\t\t {% if doc.customer_address %}\n\t\t\t\t\t\t{% set customer_address = frappe.db.get_value(\"Address\", doc.customer_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n {{ doc.customer_name }} \n\t\t\t\t\t\t{{ customer_address.address_line1 or \"\" }} \n\t\t\t\t\t\t{% if customer_address.address_line2 %}{{ customer_address.address_line2 }} {% endif %}\n\t\t\t\t\t\t{{ customer_address.city or \"\" }} {{ customer_address.state or \"\" }} {{ customer_address.pincode or \"\" }} {{ customer_address.country or \"\" }} \n\t\t\t\t\t{% endif %}\n\t\t\t\t | \n\t\t\t
| {{ _(\"No\") }} | \n\t\t\t\t\t{{ _(\"Item\") }} | \n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t{{ _(\"Item Code\") }} | \n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{{ _(\"Quantity\") }} | \n\t\t\t\t\t{{ _(\"Rate\") }} | \n\t\t\t\t\t{{ _(\"Amount\") }} | \n\t\t\t\t
| {{ loop.index }} | \n\t\t\t\t\t{{ item.item_name }} | \n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t{{ item.item_code }} | \n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }} | \n\t\t\t\t\t{{ item.get_formatted(\"net_rate\", doc) }} | \n\t\t\t\t\t\n\t\t\t\t\t\t{{ item.get_formatted(\"net_amount\", doc) }}\n\t\t\t\t\t | \n\t\t\t\t
| \n\t\t\t\t {{ _(\"Total in words\") }} \n\t\t\t\t{{ doc.in_words }} \n\t\t\t | \n\t\t\t\n\t\t\t\t \n\t\t\t | \n\t\t\t\n\t\t\t\t
| \n\t\t
| \n\t\t\t\t\t \n\t\t\t\t\t\t \n\t\t\t\t\tCustomer Name: \n\t\t\t\t\t\tBill to: \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t{{ doc.customer_name }} \n\t\t\t\t\t\t\n \t\t\t\t\t{% if doc.customer_address %}\n \t\t\t\t\t\t{% set customer_address = frappe.db.get_value(\"Address\", doc.customer_address, [\"address_line1\", \"address_line2\", \"city\", \"state\", \"pincode\", \"country\"], as_dict=True) %}\n \t\t\t\t\t\t{{ customer_address.address_line1 or \"\" }} \n\n\t\t\t\t\t\n \t\t\t\t\t\t{% if customer_address.address_line2 %}{{ customer_address.address_line2 }} {% endif %}\n \t\t\t\t\t\t{{ customer_address.city or \"\" }} {{ customer_address.state or \"\" }} {{ customer_address.pincode or \"\" }} {{ customer_address.country or \"\" }} \n \t\t\t\t\t{% endif %}\n\t\t\t\t\t\t | \n\n\t\t\t\t\n\t\t\t\t\t \n\t\t\t\t\t\t \n\t\t\t\t\tInvoice Number: \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t\t{{ doc.name }} \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t\tInvoice Date: \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t\t{{ frappe.utils.format_date(doc.posting_date) }} \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t\tPayment Due Date: \n\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t{{ frappe.utils.format_date(doc.due_date) }} \n\t\t\t\t\t | \n\t\t\t
| {{ _(\"No\") }} | \n\t\t\t\t\t{{ _(\"Item\") }} | \n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t{{ _(\"Item Code\") }} | \n\t\t\t\t\t{% endif %}\n\t\t\t\t\t{{ _(\"Quantity\") }} | \n\t\t\t\t\t{{ _(\"Rate\") }} | \n\t\t\t\t\t{{ _(\"Amount\") }} | \n\t\t\t\t||
| {{ loop.index }} | \n\t\t\t\t\t\n\t\t\t\t\t\t
| \n\t\t\t\t\t{% if item_naming_by != \"Item Code\" %}\n\t\t\t\t\t\t{{ item.item_code }} | \n\t\t\t\t\t{% endif %}\n\n\t\t\t\t\t{{ item.get_formatted(\"qty\", 0) }} {{ item.uom }} | \n\t\t\t\t\t{{ item.get_formatted(\"net_rate\", doc) }} | \n\t\t\t\t\t{{ item.get_formatted(\"net_amount\", doc) }} | \n\t\t\t\t
| {{ _(\"Sub Total:\") }} | \n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }} | \n\t\t\t\t
| \n\t\t\t\t\t\t\t{{ _(\"Discount\") }} ({{ doc.additional_discount_percentage }}%):\n\t\t\t\t\t\t | \n\t\t\t\t\t\t{{ doc.get_formatted(\"discount_amount\", doc) }} | \n\t\t\t\t\t
| {{ tax.get_formatted(\"description\") }} ({{ tax.get_formatted(\"rate\") }}%): | \n\t\t\t\t\t\t\t{{ tax.get_formatted(\"tax_amount\") }} | \n\t\t\t\t\t\t
| \n\t\t\t\t\t\t\t{{ _(\"Discount\") }} ({{ doc.additional_discount_percentage }}%):\n\t\t\t\t\t\t | \n\t\t\t\t\t\t{{ doc.get_formatted(\"discount_amount\", doc) }} | \n\t\t\t\t\t
| \n\t\t\t\t\t\t \n\t\t\t\t\t\t\t {{ _(\"In Words: \") }}{{ doc.in_words }}\n\t\t\t\t\t\t \n\t\t\t\t\t | \n\t\t\t\t\t{{ _(\"Grand Total:\") }} | \n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{{ doc.get_formatted(\"grand_total\", doc) }}\n\t\t\t\t\t\t\n\t\t\t\t\t | \n\t\t\t\t