From 3a4c1a9f9a9f357405e939c1b8d971507f9b08e9 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 26 Nov 2025 11:09:26 +0530 Subject: [PATCH 1/3] refactor: Replace use publish_realtime with msgprint for cleaner flow --- .../doctype/sales_invoice/sales_invoice.py | 106 ------- erpnext/controllers/accounts_controller.py | 107 +++++++ erpnext/public/js/print.js | 280 ++++++++---------- 3 files changed, 238 insertions(+), 255 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index fdea1b2d29e..977177cbee4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -282,59 +282,6 @@ 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 - - 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() @@ -2948,59 +2895,6 @@ 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/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3902b2f7202..48e2adef651 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,6 +7,7 @@ from collections import defaultdict import frappe from frappe import _, bold, qb, throw +from frappe.contacts.doctype.address.address import get_address_display from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied from frappe.query_builder import Criterion, DocType from frappe.query_builder.custom import ConstantColumn @@ -4167,3 +4168,109 @@ def update_gl_dict_with_regional_fields(doc, gl_dict): def update_gl_dict_with_app_based_fields(doc, gl_dict): for method in frappe.get_hooks("update_gl_dict_with_app_based_fields", default=[]): frappe.get_attr(method)(doc, gl_dict) + + +@frappe.whitelist() +def get_missing_company_details(doctype, docname): + from frappe.contacts.doctype.address.address import get_address_display_list + + company = frappe.db.get_value(doctype, docname, "company") + company_address = frappe.db.get_value(doctype, docname, "company_address") + + company_details = frappe.get_value( + "Company", 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 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", company) + address_line = address_display_list[0].get("address_line1") if address_display_list else "" + + required_fields.append(company_address) + required_fields.append(address_line) + + if all(required_fields): + return False + return { + "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": company, + "company_address": company_address, + "name": docname, + } + + +@frappe.whitelist() +def update_company_master_and_address(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 diff --git a/erpnext/public/js/print.js b/erpnext/public/js/print.js index 53f03f83bda..55c90ad773f 100644 --- a/erpnext/public/js/print.js +++ b/erpnext/public/js/print.js @@ -1,155 +1,137 @@ -let beforePrintHandled = false; +const doctype_list = ["Sales Invoice"]; +const allowed_print_formats = ["Sales Invoice Standard", "Sales Invoice with Item Image"]; +const allowed_letterheads = ["Company Letterhead", "Company Letterhead - Grey"]; -frappe.realtime.on("sales_invoice_before_print", (data) => { - let print_format = $('input[data-fieldname="print_format"]').val(); - let letterhead = $('input[data-fieldname="letterhead"]').val(); - - let allowed_print_formats = ["Sales Invoice Standard", "Sales Invoice with Item Image"]; - let allowed_letterheads = ["Company Letterhead", "Company Letterhead - Grey"]; - - if (!allowed_print_formats.includes(print_format) && !allowed_letterheads.includes(letterhead)) { - return; - } +handle_route_event(); +function handle_route_event() { const route = frappe.get_route(); + const current_doctype = route[1]; + const current_docname = route[2]; - if (!beforePrintHandled && route[0] === "print" && route[1] === "Sales Invoice") { - beforePrintHandled = true; + if (!doctype_list.includes(current_doctype)) return; - 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, + setTimeout(() => { + if (should_fetch_company_details()) { + fetch_company_details(current_doctype, current_docname); + } + }, 500); +} + +function should_fetch_company_details() { + const print_format = $('input[data-fieldname="print_format"]').val(); + const letterhead = $('input[data-fieldname="letterhead"]').val(); + + return allowed_print_formats.includes(print_format) || allowed_letterheads.includes(letterhead); +} + +function fetch_company_details(doctype, docname) { + frappe.call({ + method: "erpnext.controllers.accounts_controller.get_missing_company_details", + args: { doctype, docname }, + callback: function (r) { + if (r && r.message) { + open_company_details_dialog(r.message); + } + }, + }); +} + +function open_company_details_dialog(data) { + const dialog = new frappe.ui.Dialog({ + title: "Enter Company Details", + fields: build_dialog_fields(data), + primary_action_label: "Save", + primary_action(values) { + save_company_details(dialog, data, values); + }, + }); + + dialog.show(); +} + +function build_dialog_fields(data) { + return [ + make_field("Company Logo", "company_logo", "Attach Image", data.company_logo), + make_field("Website", "website", "Data", data.website), + make_field("Phone No", "phone_no", "Data", data.phone_no), + { + label: "Email", + fieldname: "email", + fieldtype: "Data", + options: "Email", + reqd: data.email ? 0 : 1, + hidden: data.email ? 1 : 0, + }, + { fieldtype: "Section Break" }, + + make_field("Address Title", "address_title", "Data", data.address_line), + { + 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, + }, + make_field("Address Line 1", "address_line1", "Data", data.address_line), + make_field("Address Line 2", "address_line2", "Data", data.address_line, false), + make_field("City", "city", "Data", data.address_line), + make_field("State", "state", "Data", data.address_line, false), + { + label: "Country", + fieldname: "country", + fieldtype: "Link", + options: "Country", + reqd: data.address_line ? 0 : 1, + hidden: data.address_line ? 1 : 0, + }, + make_field("Postal Code", "pincode", "Data", data.address_line, false), + + { + label: "Select Company Address", + fieldname: "company_address", + fieldtype: "Link", + options: "Address", + get_query: () => ({ + query: "frappe.contacts.doctype.address.address.address_query", + filters: { + link_doctype: "Company", + link_name: data.company, }, - { - label: "Website", - fieldname: "website", - fieldtype: "Data", - 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 1", - fieldname: "address_line1", - fieldtype: "Data", - reqd: data.address_line ? 0 : 1, - hidden: data.address_line ? 1 : 0, - }, - { - label: "Address Line 2", - fieldname: "address_line2", - fieldtype: "Data", - 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 { - query: "frappe.contacts.doctype.address.address.address_query", - 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; - } -}); + }), + reqd: data.address_line && !data.company_address ? 1 : 0, + hidden: data.address_line && !data.company_address ? 0 : 1, + }, + ]; +} + +function make_field(label, fieldname, fieldtype, existing_value, required_if_empty = true) { + return { + label, + fieldname, + fieldtype, + reqd: existing_value ? 0 : required_if_empty ? 1 : 0, + hidden: existing_value ? 1 : 0, + }; +} + +function save_company_details(dialog, data, values) { + frappe.call({ + method: "erpnext.controllers.accounts_controller.update_company_master_and_address", + args: { + name: data.name, + company: data.company, + details: values, + }, + callback() { + dialog.hide(); + frappe.msgprint("Updating details."); + + setTimeout(() => { + location.reload(); + }, 1000); + }, + }); +} From b808a51d8f6a664f8cbb8fa4ad657f7fef5e80f8 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Sun, 30 Nov 2025 17:38:50 +0530 Subject: [PATCH 2/3] refactor: Make labels translatable --- erpnext/public/js/print.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/erpnext/public/js/print.js b/erpnext/public/js/print.js index 55c90ad773f..4afd34e16dd 100644 --- a/erpnext/public/js/print.js +++ b/erpnext/public/js/print.js @@ -39,9 +39,9 @@ function fetch_company_details(doctype, docname) { function open_company_details_dialog(data) { const dialog = new frappe.ui.Dialog({ - title: "Enter Company Details", + title: __("Enter Company Details"), fields: build_dialog_fields(data), - primary_action_label: "Save", + primary_action_label: __("Save"), primary_action(values) { save_company_details(dialog, data, values); }, @@ -52,11 +52,11 @@ function open_company_details_dialog(data) { function build_dialog_fields(data) { return [ - make_field("Company Logo", "company_logo", "Attach Image", data.company_logo), - make_field("Website", "website", "Data", data.website), - make_field("Phone No", "phone_no", "Data", data.phone_no), + make_field(__("Company Logo"), "company_logo", "Attach Image", data.company_logo), + make_field(__("Website"), "website", "Data", data.website), + make_field(__("Phone No"), "phone_no", "Data", data.phone_no), { - label: "Email", + label: __("Email"), fieldname: "email", fieldtype: "Data", options: "Email", @@ -65,9 +65,9 @@ function build_dialog_fields(data) { }, { fieldtype: "Section Break" }, - make_field("Address Title", "address_title", "Data", data.address_line), + make_field(__("Address Title"), "address_title", "Data", data.address_line), { - label: "Address Type", + label: __("Address Type"), fieldname: "address_type", fieldtype: "Select", options: ["Billing", "Shipping"], @@ -75,22 +75,22 @@ function build_dialog_fields(data) { reqd: data.address_line ? 0 : 1, hidden: data.address_line ? 1 : 0, }, - make_field("Address Line 1", "address_line1", "Data", data.address_line), - make_field("Address Line 2", "address_line2", "Data", data.address_line, false), - make_field("City", "city", "Data", data.address_line), - make_field("State", "state", "Data", data.address_line, false), + make_field(__("Address Line 1"), "address_line1", "Data", data.address_line), + make_field(__("Address Line 2"), "address_line2", "Data", data.address_line, false), + make_field(__("City"), "city", "Data", data.address_line), + make_field(__("State"), "state", "Data", data.address_line, false), { - label: "Country", + label: __("Country"), fieldname: "country", fieldtype: "Link", options: "Country", reqd: data.address_line ? 0 : 1, hidden: data.address_line ? 1 : 0, }, - make_field("Postal Code", "pincode", "Data", data.address_line, false), + make_field(__("Postal Code"), "pincode", "Data", data.address_line, false), { - label: "Select Company Address", + label: __("Select Company Address"), fieldname: "company_address", fieldtype: "Link", options: "Address", @@ -127,7 +127,7 @@ function save_company_details(dialog, data, values) { }, callback() { dialog.hide(); - frappe.msgprint("Updating details."); + frappe.msgprint(__("Updating details.")); setTimeout(() => { location.reload(); From 5cfd7ec32aecd1a4df8b378599b425c177c1cf7c Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Sun, 30 Nov 2025 18:30:47 +0530 Subject: [PATCH 3/3] refactor: generalize popup for multiple doctypes --- erpnext/controllers/accounts_controller.py | 51 +++++++++++++++------- erpnext/public/js/print.js | 26 +++++++---- 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 48e2adef651..fb809473962 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -4175,7 +4175,10 @@ def get_missing_company_details(doctype, docname): from frappe.contacts.doctype.address.address import get_address_display_list company = frappe.db.get_value(doctype, docname, "company") - company_address = frappe.db.get_value(doctype, docname, "company_address") + if doctype == "Purchase Order": + company_address = frappe.db.get_value(doctype, docname, "billing_address") + else: + company_address = frappe.db.get_value(doctype, docname, "company_address") company_details = frappe.get_value( "Company", company, ["company_logo", "website", "phone_no", "email"], as_dict=True @@ -4195,7 +4198,7 @@ def get_missing_company_details(doctype, docname): ) return - if not company_address and not frappe.has_permission("Sales Invoice", "write", throw=False): + if not company_address and not frappe.has_permission(doctype, "write", throw=False): frappe.msgprint( _( "Company Address is missing. You don't have permission to update it. Please contact your System Manager." @@ -4224,7 +4227,7 @@ def get_missing_company_details(doctype, docname): @frappe.whitelist() -def update_company_master_and_address(name, company, details): +def update_company_master_and_address(current_doctype, name, company, details): from frappe.utils import validate_email_address if isinstance(details, str): @@ -4259,18 +4262,36 @@ def update_company_master_and_address(name, company, details): 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 + update_doc_company_address(current_doctype, name, company_address, details) - 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() +def update_doc_company_address(current_doctype, docname, company_address, details): + if not company_address: + return - return True + address_field_map = { + "Purchase Order": ("billing_address", "billing_address_display"), + "Sales Invoice": ("company_address", "company_address_display"), + "Delivery Note": ("company_address", "company_address_display"), + "POS Invoice": ("company_address", "company_address_display"), + } + + address_field, display_field = address_field_map.get( + current_doctype, ("company_address", "company_address_display") + ) + + current_display = frappe.db.get_value(current_doctype, docname, display_field) + + if current_display and not details.get("address_line1"): + return + + from frappe.query_builder import DocType + + DocType = DocType(current_doctype) + + ( + frappe.qb.update(DocType) + .set(getattr(DocType, address_field), company_address) + .set(getattr(DocType, display_field), get_address_display(company_address)) + .where(DocType.name == docname) + ).run() diff --git a/erpnext/public/js/print.js b/erpnext/public/js/print.js index 4afd34e16dd..105a580aed6 100644 --- a/erpnext/public/js/print.js +++ b/erpnext/public/js/print.js @@ -1,5 +1,14 @@ -const doctype_list = ["Sales Invoice"]; -const allowed_print_formats = ["Sales Invoice Standard", "Sales Invoice with Item Image"]; +const doctype_list = ["Sales Invoice", "Delivery Note", "Purchase Order", "POS Invoice"]; +const allowed_print_formats = [ + "Sales Invoice Standard", + "Sales Invoice with Item Image", + "Delivery Note Standard", + "Delivery Note with Item Image", + "Purchase Order Standard", + "Purchase Order with Item Image", + "POS Invoice Standard", + "POS Invoice with Item Image", +]; const allowed_letterheads = ["Company Letterhead", "Company Letterhead - Grey"]; handle_route_event(); @@ -25,25 +34,25 @@ function should_fetch_company_details() { return allowed_print_formats.includes(print_format) || allowed_letterheads.includes(letterhead); } -function fetch_company_details(doctype, docname) { +function fetch_company_details(current_doctype, current_docname) { frappe.call({ method: "erpnext.controllers.accounts_controller.get_missing_company_details", - args: { doctype, docname }, + args: { doctype: current_doctype, docname: current_docname }, callback: function (r) { if (r && r.message) { - open_company_details_dialog(r.message); + open_company_details_dialog(r.message, current_doctype); } }, }); } -function open_company_details_dialog(data) { +function open_company_details_dialog(data, current_doctype) { const dialog = new frappe.ui.Dialog({ title: __("Enter Company Details"), fields: build_dialog_fields(data), primary_action_label: __("Save"), primary_action(values) { - save_company_details(dialog, data, values); + save_company_details(dialog, data, values, current_doctype); }, }); @@ -117,13 +126,14 @@ function make_field(label, fieldname, fieldtype, existing_value, required_if_emp }; } -function save_company_details(dialog, data, values) { +function save_company_details(dialog, data, values, current_doctype) { frappe.call({ method: "erpnext.controllers.accounts_controller.update_company_master_and_address", args: { name: data.name, company: data.company, details: values, + current_doctype: current_doctype, }, callback() { dialog.hide();