From bfff945fb130127c63fdfa9bb2a960da93e20c83 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Wed, 3 Sep 2025 16:53:20 +0530 Subject: [PATCH 001/108] fix(production plan): filter sales orders by item --- .../manufacturing/doctype/production_plan/production_plan.js | 1 + .../manufacturing/doctype/production_plan/production_plan.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 9a39b3af84b..1ce9d9ca889 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -34,6 +34,7 @@ frappe.ui.form.on("Production Plan", { query: "erpnext.manufacturing.doctype.production_plan.production_plan.sales_order_query", filters: { company: frm.doc.company, + item_code: frm.doc.item_code, }, }; }); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 42ad3193cf4..7a303d5750c 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -2080,6 +2080,9 @@ def sales_order_query(doctype=None, txt=None, searchfield=None, start=None, page if filters.get("sales_orders"): query = query.where(so_table.name.isin(filters.get("sales_orders"))) + if filters.get("item_code"): + query = query.where(table.item_code == filters.get("item_code")) + if txt: query = query.where(table.parent.like(f"%{txt}%")) From 4b7cb6bfad622150e9f644c88be687cc756c05a1 Mon Sep 17 00:00:00 2001 From: "El-Shafei H." Date: Mon, 8 Sep 2025 13:46:52 +0300 Subject: [PATCH 002/108] feat: add employee name to session user --- erpnext/startup/boot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 8dccac80b88..58205918481 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -84,3 +84,4 @@ def bootinfo(bootinfo): employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") if employee: bootinfo["user"]["employee"] = employee + frappe.session.data.employee = employee From 0948358bb3fe62053ff4fcac285ab7f5743465ea Mon Sep 17 00:00:00 2001 From: Rehan Ansari Date: Mon, 6 Oct 2025 01:29:54 +0530 Subject: [PATCH 003/108] fix: prevent empty Create dropdown when In Process --- .../doctype/production_plan/production_plan.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 9a39b3af84b..4517314748f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -115,6 +115,8 @@ frappe.ui.form.on("Production Plan", { __("View") ); + let has_create_buttons = false; + if (frm.doc.status !== "Completed") { if (frm.doc.status === "Closed") { frm.add_custom_button( @@ -144,6 +146,7 @@ frappe.ui.form.on("Production Plan", { }, __("Create") ); + has_create_buttons = true; } if ( @@ -158,10 +161,11 @@ frappe.ui.form.on("Production Plan", { }, __("Create") ); + has_create_buttons = true; } } - if (frm.doc.status !== "Closed") { + if (has_create_buttons && frm.doc.status !== "Closed") { frm.page.set_inner_btn_group_as_primary(__("Create")); } } From dbab718aaa76a56cd2b1900b0e781ba90b6666e5 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 7 Oct 2025 11:18:30 +0530 Subject: [PATCH 004/108] fix(process statement of accounts): allow renaming --- .../process_statement_of_accounts.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index ea1b61a31d4..a546fc4ff6e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_rename": 1, "autoname": "Prompt", "creation": "2020-05-22 16:46:18.712954", "doctype": "DocType", @@ -416,7 +417,7 @@ } ], "links": [], - "modified": "2025-09-03 14:24:43.608565", + "modified": "2025-10-07 11:17:05.444394", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", From d610d1dccd80a1e8f657f4b9b975533467b5fc12 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 7 Oct 2025 12:29:24 +0530 Subject: [PATCH 005/108] feat(process statement of accounts): added more frequency options for auto email --- .../process_statement_of_accounts.json | 4 ++-- .../process_statement_of_accounts.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index a546fc4ff6e..dd021edd7f2 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -70,7 +70,7 @@ "fieldname": "frequency", "fieldtype": "Select", "label": "Frequency", - "options": "Weekly\nMonthly\nQuarterly" + "options": "Daily\nWeekly\nBiweekly\nMonthly\nQuarterly" }, { "fieldname": "company", @@ -417,7 +417,7 @@ } ], "links": [], - "modified": "2025-10-07 11:17:05.444394", + "modified": "2025-10-07 12:19:20.719898", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 96389f67e1e..9cf27216b1e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -8,7 +8,7 @@ import frappe from frappe import _ from frappe.desk.reportview import get_match_cond from frappe.model.document import Document -from frappe.utils import add_days, add_months, format_date, getdate, today +from frappe.utils import add_days, add_months, add_to_date, format_date, getdate, today from frappe.utils.jinja import validate_template from frappe.utils.pdf import get_pdf from frappe.www.printview import get_print_style @@ -55,7 +55,7 @@ class ProcessStatementOfAccounts(Document): enable_auto_email: DF.Check filter_duration: DF.Int finance_book: DF.Link | None - frequency: DF.Literal["Weekly", "Monthly", "Quarterly"] + frequency: DF.Literal["Daily", "Weekly", "Biweekly", "Monthly", "Quarterly"] from_date: DF.Date | None ignore_cr_dr_notes: DF.Check ignore_exchange_rate_revaluation_journals: DF.Check @@ -555,8 +555,9 @@ def send_emails(document_name, from_scheduler=False, posting_date=None): if doc.enable_auto_email and from_scheduler: new_to_date = getdate(posting_date or today()) - if doc.frequency == "Weekly": - new_to_date = add_days(new_to_date, 7) + if doc.frequency in ("Daily", "Weekly", "Biweekly"): + frequency = {"Daily": 1, "Weekly": 7, "Biweekly": 14} + new_to_date = add_days(new_to_date, frequency[doc.frequency]) else: new_to_date = add_months(new_to_date, 1 if doc.frequency == "Monthly" else 3) new_from_date = add_months(new_to_date, -1 * doc.filter_duration) From 067863810629169698acc7fca5ab69370e247fbf Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 7 Oct 2025 12:46:09 +0530 Subject: [PATCH 006/108] fix: preserve address if present --- erpnext/public/js/controllers/buying.js | 14 ++++++++------ erpnext/public/js/controllers/transaction.js | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 3e298afe658..ed363edfb96 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -174,13 +174,15 @@ erpnext.buying = { shipping_address: this.frm.doc.shipping_address, }, callback: (r) => { - this.frm.set_value("billing_address", r.message.primary_address || ""); + if (!this.frm.doc.billing_address) + this.frm.set_value("billing_address", r.message.primary_address || ""); - if (!frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) return; - this.frm.set_value( - "shipping_address", - r.message.shipping_address || this.frm.doc.shipping_address || "" - ); + if ( + !frappe.meta.has_field(this.frm.doc.doctype, "shipping_address") || + this.frm.doc.shipping_address + ) + return; + this.frm.set_value("shipping_address", r.message.shipping_address || ""); }, }); erpnext.utils.set_letter_head(this.frm); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 844a768f4f7..d98c95b80b0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1170,7 +1170,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if ( frappe.meta.get_docfield(this.frm.doctype, "shipping_address") && - ["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype) + ["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype) && + !this.frm.doc.shipping_address ) { let is_drop_ship = me.frm.doc.items.some((item) => item.delivered_by_supplier); From f00a63b69dcbe10c5f5e40e13112e33541f1a25f Mon Sep 17 00:00:00 2001 From: ljain112 Date: Tue, 7 Oct 2025 12:46:30 +0530 Subject: [PATCH 007/108] perf: optimize validate_qty method to eliminate N+1 query problem --- erpnext/controllers/status_updater.py | 89 +++++++++++++++++++++------ 1 file changed, 69 insertions(+), 20 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index fe0a273b6ed..c366a4a7cf2 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -265,6 +265,8 @@ class StatusUpdater(Document): # if target_ref_field is not specified, the programmer does not want to validate qty / amount continue + items_to_validate = [] + # get unique transactions to update for d in self.get_all_children(): if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"): @@ -286,31 +288,63 @@ class StatusUpdater(Document): ) if d.doctype == args["source_dt"] and d.get(args["join_field"]): - args["name"] = d.get(args["join_field"]) - - is_from_pp = ( - hasattr(d, "production_plan_sub_assembly_item") - and frappe.db.get_value( - "Production Plan Sub Assembly Item", - d.production_plan_sub_assembly_item, - "type_of_manufacturing", + items_to_validate.append( + frappe._dict( + { + "name": d.get(args["join_field"]), + "production_plan_sub_assembly_item": d.get( + "production_plan_sub_assembly_item" + ), + "idx": d.idx, + "child_doc": d, + } ) - == "Subcontract" ) - args["item_code"] = "production_item" if is_from_pp else "item_code" - # get all qty where qty > target_field - item = frappe.db.sql( - """select `{item_code}` as item_code, `{target_ref_field}`, - `{target_field}`, parenttype, parent from `tab{target_dt}` - where `{target_ref_field}` < `{target_field}` - and name=%s and docstatus=1""".format(**args), - args["name"], - as_dict=1, + if items_to_validate: + pp_sub_assembly_items = [ + item.production_plan_sub_assembly_item + for item in items_to_validate + if item.production_plan_sub_assembly_item + ] + + pp_subcontract_items = [] + if pp_sub_assembly_items: + pp_subcontract_items = frappe.db.get_all( + "Production Plan Sub Assembly Item", + filters={ + "name": ("in", pp_sub_assembly_items), + "type_of_manufacturing": "Subcontract", + }, + pluck="name", ) + + regular_items = [] + pp_items = [] + + for item in items_to_validate: + if item.production_plan_sub_assembly_item in pp_subcontract_items: + pp_items.append(item.name) + else: + regular_items.append(item.name) + + item_details = [] + + # Query regular items with item_code field + if regular_items: + item_details.extend(self.fetch_items_with_pending_qty(args, "item_code", regular_items)) + + # Query production plan items with production_item field + if pp_items: + item_details.extend(self.fetch_items_with_pending_qty(args, "production_item", pp_items)) + + item_lookup = {item.name: item for item in item_details} + + for child_item in items_to_validate: + item = item_lookup.get(child_item.name) + if item: - item = item[0] - item["idx"] = d.idx + item["idx"] = child_item.idx item["target_ref_field"] = args["target_ref_field"].replace("_", " ") # if not item[args['target_ref_field']]: @@ -323,6 +357,21 @@ class StatusUpdater(Document): elif item[args["target_ref_field"]]: self.check_overflow_with_allowance(item, args) + def fetch_items_with_pending_qty(self, args, item_field, items): + return frappe.db.sql( + """select name,`{item_code}` as item_code, `{target_ref_field}`, + `{target_field}`, parenttype, parent from `tab{target_dt}` + where `{target_ref_field}` < `{target_field}` + and name in %(names)s and docstatus=1""".format( + item_code=item_field, + target_ref_field=args["target_ref_field"], + target_field=args["target_field"], + target_dt=args["target_dt"], + ), + {"names": items}, + as_dict=1, + ) + def check_overflow_with_allowance(self, item, args): """ Checks if there is overflow condering a relaxation allowance From 4a4c2188ec5818c02ee83d34e5f2ef25e450c89a Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Tue, 7 Oct 2025 16:04:23 +0530 Subject: [PATCH 008/108] fix(process statement of accounts): naming of reports --- .../process_statement_of_accounts.html | 2 +- .../process_statement_of_accounts_accounts_receivable.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index c3603290f47..5aac9d902e1 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -13,7 +13,7 @@ {% endif %} -

{{ _("STATEMENT OF ACCOUNTS") }}

+

{{ _("GENERAL LEDGER") }}

{% if filters.party[0] == filters.party_name[0] %}
{{ _("Customer: ") }} {{ filters.party_name[0] }}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html index 441a4b3da43..89ea90f6eb7 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html @@ -23,7 +23,7 @@ {% endif %}
-

{{ _(report.report_name) }}

+

{{ _("STATEMENT OF ACCOUNTS") }}

{{ filters.customer_name }}

From 5d0958c5b1811fcb8e0331bf0a4b3fa88a49195f Mon Sep 17 00:00:00 2001 From: "El-Shafei H." Date: Wed, 8 Oct 2025 09:51:13 +0300 Subject: [PATCH 009/108] feat: Cache employee name in session data on boot --- erpnext/startup/boot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 1d56078453b..64a675111ca 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -85,3 +85,5 @@ def bootinfo(bootinfo): if employee: bootinfo["user"]["employee"] = employee frappe.session.data.employee = employee + else: + frappe.session.data.employee = "" From 630d8732143b1273cc1f7ee4f2d350f80a888d27 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Oct 2025 18:27:39 +0530 Subject: [PATCH 010/108] fix: incorrect field valuation_rate --- erpnext/stock/stock_ledger.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bab68615d30..80d35ad3556 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1074,16 +1074,15 @@ class update_entries_after: for d in sabb_data: incoming_rate = get_incoming_rate_for_serial_and_batch(self.item_code, d, sn_obj) - - if flt(incoming_rate, self.currency_precision) == flt( - d.valuation_rate, self.currency_precision - ) and not getattr(d, "stock_queue", None): - continue - amount = incoming_rate * flt(d.qty) tot_amt += flt(amount) total_qty += flt(d.qty) + if flt(incoming_rate, self.currency_precision) == flt( + d.incoming_rate, self.currency_precision + ) and not getattr(d, "stock_queue", None): + continue + values_to_update = { "incoming_rate": incoming_rate, "stock_value_difference": amount, From ff2faf36a7640cda4a9fe4bfb5b1380752af7f97 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Oct 2025 18:37:40 +0530 Subject: [PATCH 011/108] fix: batch qty for expired batches --- erpnext/stock/doctype/batch/batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index ad8218cfcfb..7a12844f1c6 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -158,7 +158,7 @@ class Batch(Document): @frappe.whitelist() def recalculate_batch_qty(self): - batches = get_batch_qty(batch_no=self.name, item_code=self.item) + batches = get_batch_qty(batch_no=self.name, item_code=self.item, for_stock_levels=True) batch_qty = 0.0 if batches: for row in batches: From aab6271b140466605f2d61cbf8ce5f64aee01bdd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Oct 2025 19:06:53 +0530 Subject: [PATCH 012/108] fix: incorrect qty in stock levels --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 386e89af49b..91bbc6abdcb 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -2337,15 +2337,15 @@ def get_reserved_batches_for_sre(kwargs) -> dict: if kwargs.batch_no: if isinstance(kwargs.batch_no, list): - query = query.where(sb_entry.batch_no.notin(kwargs.batch_no)) + query = query.where(sb_entry.batch_no.isin(kwargs.batch_no)) else: - query = query.where(sb_entry.batch_no != kwargs.batch_no) + query = query.where(sb_entry.batch_no == kwargs.batch_no) if kwargs.warehouse: if isinstance(kwargs.warehouse, list): - query = query.where(sre.warehouse.notin(kwargs.warehouse)) + query = query.where(sre.warehouse.isin(kwargs.warehouse)) else: - query = query.where(sre.warehouse != kwargs.warehouse) + query = query.where(sre.warehouse == kwargs.warehouse) if kwargs.ignore_voucher_nos: query = query.where(sre.name.notin(kwargs.ignore_voucher_nos)) From 128e243945457d36e720ffb4633113fdba172d93 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Oct 2025 21:46:17 +0530 Subject: [PATCH 013/108] fix: Reset Raw Materials Table button not working --- erpnext/controllers/subcontracting_controller.py | 6 ++++++ .../subcontracting_receipt/subcontracting_receipt.js | 4 ++++ .../subcontracting_receipt/subcontracting_receipt.json | 6 ++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index cf3782c7e4b..db42d11ab0a 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -202,6 +202,9 @@ class SubcontractingController(StockController): self.set(self.raw_material_table, []) return + if not self.get(self.raw_material_table): + return + item_dict = self.__get_data_before_save() if not item_dict: return True @@ -657,6 +660,9 @@ class SubcontractingController(StockController): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward from erpnext.stock.get_item_details import get_filtered_serial_nos + if self.is_return: + return + for row in self.supplied_items: item_details = frappe.get_cached_value( "Item", row.rm_item_code, ["has_batch_no", "has_serial_no"], as_dict=1 diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js index c9fe457ef79..fee1cac2542 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js @@ -336,6 +336,10 @@ frappe.ui.form.on("Subcontracting Receipt", { reset_raw_materials_table: (frm) => { frm.clear_table("supplied_items"); + frm.doc.__unsaved = true; + if (!frm.doc.set_posting_time) { + frm.set_value("posting_time", frappe.datetime.now_time()); + } frm.call({ method: "reset_raw_materials", diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json index f3a37cc8bb1..79b46ec146a 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json @@ -649,6 +649,7 @@ "label": "Raw Materials Actions" }, { + "description": "Click this button if you encounter a negative stock error for a serial or batch item. The system will fetch the available serials or batches automatically.", "fieldname": "reset_raw_materials_table", "fieldtype": "Button", "label": "Reset Raw Materials Table" @@ -678,7 +679,7 @@ "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2024-12-06 15:24:38.384232", + "modified": "2025-10-08 21:43:27.065640", "modified_by": "Administrator", "module": "Subcontracting", "name": "Subcontracting Receipt", @@ -739,6 +740,7 @@ "write": 1 } ], + "row_format": "Dynamic", "search_fields": "status, posting_date, supplier", "show_name_in_global_search": 1, "sort_field": "creation", @@ -747,4 +749,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 946073cfd93c654b075e23798a325667eafc486e Mon Sep 17 00:00:00 2001 From: Jaswanth Sriram Date: Thu, 2 Oct 2025 14:13:45 +0530 Subject: [PATCH 014/108] perf: avoid unnecessary set_dynamic_labels updates --- erpnext/public/js/controllers/transaction.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 32c5451b6f7..7b41afcb621 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1679,6 +1679,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ); var company_currency = this.get_company_currency(); + + if ( + this._last_company_currency === company_currency && + this._last_price_list_currency === this.frm.doc.price_list_currency + ) { + return; + } + + this._last_company_currency = company_currency; + this._last_price_list_currency = this.frm.doc.price_list_currency; + this.change_form_labels(company_currency); this.change_grid_labels(company_currency); this.frm.refresh_fields(); From 13ce7279a87a538fae272748b0e84e4efb673b7c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 9 Oct 2025 01:00:31 +0530 Subject: [PATCH 015/108] fix: sales return for product bundle items --- .../controllers/sales_and_purchase_return.py | 22 +++++++++++++--- erpnext/controllers/selling_controller.py | 9 ++++++- erpnext/controllers/stock_controller.py | 26 ++++++++++++++++++- .../serial_and_batch_bundle.py | 14 ++++++++++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index ce7b4da1ed2..218c96dc37a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -857,10 +857,10 @@ def get_available_serial_batches(field, doctype, reference_ids, is_rejected=Fals if not _bundle_ids: return frappe._dict({}) - return get_serial_batches_based_on_bundle(field, _bundle_ids) + return get_serial_batches_based_on_bundle(doctype, field, _bundle_ids) -def get_serial_batches_based_on_bundle(field, _bundle_ids): +def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids): available_dict = frappe._dict({}) batch_serial_nos = frappe.get_all( "Serial and Batch Bundle", @@ -872,6 +872,7 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids): "`tabSerial and Batch Bundle`.`voucher_detail_no`", "`tabSerial and Batch Bundle`.`voucher_type`", "`tabSerial and Batch Bundle`.`voucher_no`", + "`tabSerial and Batch Bundle`.`item_code`", ], filters=[ ["Serial and Batch Bundle", "name", "in", _bundle_ids], @@ -885,6 +886,9 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids): if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"): key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field) + if doctype == "Packed Item": + key = (row.item_code, key) + if row.voucher_type in ["Sales Invoice", "Delivery Note"]: row.qty = -1 * row.qty @@ -913,6 +917,8 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids): def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False): filters = {"docstatus": 1, "name": ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} + if doctype == "Packed Item": + filters = {"docstatus": 1, field: ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} pluck_field = "serial_and_batch_bundle" if is_rejected: @@ -926,10 +932,14 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False pluck=pluck_field, ) + if _bundle_ids and doctype == "Packed Item": + return _bundle_ids + if not _bundle_ids: return {} - del filters["name"] + if "name" in filters: + del filters["name"] filters[field] = ("in", reference_ids) @@ -976,6 +986,9 @@ def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field if not qty_field: qty_field = "stock_qty" + if not hasattr(row, qty_field): + qty_field = "qty" + if not warehouse_field: warehouse_field = "warehouse" @@ -1065,6 +1078,9 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f if not qty_field: qty_field = "stock_qty" + if not hasattr(child_doc, qty_field): + qty_field = "qty" + warehouse = child_doc.get(warehouse_field) if parent_doc.get("is_internal_customer"): warehouse = child_doc.get("target_warehouse") diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index a0c53ad0ece..bda058f9f9a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -519,8 +519,15 @@ class SellingController(StockController): if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"): continue + item_details = frappe.get_cached_value( + "Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1 + ) + if not self.get("return_against") or ( - get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return") + get_valuation_method(d.item_code) == "Moving Average" + and self.get("is_return") + and not item_details.has_serial_no + and not item_details.has_batch_no ): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty")) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 84d22c54f8c..7963007c0bc 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -360,10 +360,20 @@ class StockController(AccountsController): return child_doctype = self.doctype + " Item" + if table_name == "packed_items": + field = "parent_detail_docname" + child_doctype = "Packed Item" + available_dict = available_serial_batch_for_return(field, child_doctype, reference_ids) for row in self.get(table_name): - if data := available_dict.get(row.get(field)): + value = row.get(field) + if table_name == "packed_items" and row.get("parent_detail_docname"): + value = self.get_value_for_packed_item(row) + if not value: + continue + + if data := available_dict.get(value): data = filter_serial_batches(self, data, row) bundle = make_serial_batch_bundle_for_return(data, row, self) row.db_set( @@ -379,6 +389,14 @@ class StockController(AccountsController): "incoming_rate", frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate") ) + def get_value_for_packed_item(self, row): + parent_items = self.get("items", {"name": row.parent_detail_docname}) + if parent_items: + ref = parent_items[0].get("dn_detail") + return (row.item_code, ref) + + return None + def get_reference_ids(self, table_name, qty_field=None, bundle_field=None) -> tuple[str, list[str]]: field = { "Sales Invoice": "sales_invoice_item", @@ -413,6 +431,12 @@ class StockController(AccountsController): ): reference_ids.append(row.get(field)) + if table_name == "packed_items" and row.get("parent_detail_docname"): + parent_rows = self.get("items", {"name": row.parent_detail_docname}) or [] + for d in parent_rows: + if d.get(field) and not d.get(bundle_field): + reference_ids.append(d.get(field)) + return field, reference_ids @frappe.request_cache diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 386e89af49b..ec2bfccd5a4 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -638,6 +638,17 @@ class SerialandBatchBundle(Document): if not rate and self.voucher_detail_no and self.voucher_no: rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field) + is_packed_item = False + if rate is None and child_table == "Delivery Note Item": + rate = frappe.db.get_value( + "Packed Item", + self.voucher_detail_no, + "incoming_rate", + ) + + if rate is not None: + is_packed_item = True + stock_queue = [] batches = [] if prev_sle and prev_sle.stock_queue: @@ -659,6 +670,9 @@ class SerialandBatchBundle(Document): elif (d.incoming_rate == rate) and not stock_queue and d.qty and d.stock_value_difference: continue + if is_packed_item and d.incoming_rate: + rate = d.incoming_rate + d.incoming_rate = flt(rate) if d.qty: d.stock_value_difference = flt(d.qty) * d.incoming_rate From 83d575206b2373bc04a1f3fc51e2a79dc6ebc8e6 Mon Sep 17 00:00:00 2001 From: "El-Shafei H." Date: Thu, 9 Oct 2025 08:25:58 +0300 Subject: [PATCH 016/108] feat: cache employee name in session data on boot --- erpnext/startup/boot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 64a675111ca..05d8cf1f2f7 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -81,9 +81,8 @@ def update_page_info(bootinfo): def bootinfo(bootinfo): if bootinfo.get("user") and bootinfo["user"].get("name"): bootinfo["user"]["employee"] = "" + frappe.session.data.employee = "" employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name") if employee: bootinfo["user"]["employee"] = employee frappe.session.data.employee = employee - else: - frappe.session.data.employee = "" From b48bff202929dc87ef3629f5eaefed35e2352a73 Mon Sep 17 00:00:00 2001 From: sokumon Date: Thu, 9 Oct 2025 13:01:46 +0530 Subject: [PATCH 017/108] fix: format workstation link correctly --- erpnext/public/js/templates/visual_plant_floor_template.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/templates/visual_plant_floor_template.html b/erpnext/public/js/templates/visual_plant_floor_template.html index f4be742267b..273a5406eeb 100644 --- a/erpnext/public/js/templates/visual_plant_floor_template.html +++ b/erpnext/public/js/templates/visual_plant_floor_template.html @@ -5,7 +5,7 @@ {{row.status}} -
+
{% if(row.status_image) { %} From c9d98eb4f0f782f28ac160e29f44058d8ec0206a Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Thu, 9 Oct 2025 13:49:06 +0530 Subject: [PATCH 018/108] fix: fixed asset register showing opening entries --- .../assets/report/fixed_asset_register/fixed_asset_register.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 7238b13e9f0..2103379df93 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -319,6 +319,7 @@ def get_asset_value_adjustment_map(filters, finance_book): .select(asset.name.as_("asset"), Sum(gle.debit - gle.credit).as_("adjustment_amount")) .where(gle.account == aca.fixed_asset_account) .where(gle.is_cancelled == 0) + .where(gle.is_opening == "No") .where(company.name == filters.company) .where(asset.docstatus == 1) ) From 1d57bbca1105a041a8be88d0daf6f593465cbd7f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 9 Oct 2025 13:56:04 +0530 Subject: [PATCH 019/108] test: test case for sales return for product bundle --- .../controllers/sales_and_purchase_return.py | 26 +++- erpnext/controllers/selling_controller.py | 3 + .../delivery_note/test_delivery_note.py | 121 ++++++++++++++++++ .../serial_and_batch_bundle.py | 2 +- 4 files changed, 150 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 218c96dc37a..16b86eeb525 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -854,6 +854,7 @@ def available_serial_batch_for_return(field, doctype, reference_ids, is_rejected def get_available_serial_batches(field, doctype, reference_ids, is_rejected=False): _bundle_ids = get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=is_rejected) + if not _bundle_ids: return frappe._dict({}) @@ -887,6 +888,13 @@ def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids): key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field) if doctype == "Packed Item": + if key is None: + key = frappe.get_cached_value("Packed Item", row.voucher_detail_no, field) + if row.voucher_type == "Delivery Note": + key = frappe.get_cached_value("Delivery Note Item", key, "dn_detail") + elif row.voucher_type == "Sales Invoice": + key = frappe.get_cached_value("Sales Invoice Item", key, "sales_invoice_item") + key = (row.item_code, key) if row.voucher_type in ["Sales Invoice", "Delivery Note"]: @@ -918,7 +926,7 @@ def get_serial_batches_based_on_bundle(doctype, field, _bundle_ids): def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False): filters = {"docstatus": 1, "name": ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} if doctype == "Packed Item": - filters = {"docstatus": 1, field: ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} + filters = get_filters_for_packed_item(field, reference_ids) pluck_field = "serial_and_batch_bundle" if is_rejected: @@ -982,6 +990,22 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False return _bundle_ids +def get_filters_for_packed_item(field, reference_ids): + names = [] + filters = {"docstatus": 1, "dn_detail": ("in", reference_ids)} + if dns := frappe.get_all("Delivery Note Item", filters=filters, pluck="name"): + names.extend(dns) + + filters = {"docstatus": 1, "sales_invoice_item": ("in", reference_ids)} + if sis := frappe.get_all("Sales Invoice Item", filters=filters, pluck="name"): + names.extend(sis) + + if names: + reference_ids.extend(names) + + return {"docstatus": 1, field: ("in", reference_ids), "serial_and_batch_bundle": ("is", "set")} + + def filter_serial_batches(parent_doc, data, row, warehouse_field=None, qty_field=None): if not qty_field: qty_field = "stock_qty" diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index bda058f9f9a..8b0a0f19f9b 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -1006,6 +1006,9 @@ def set_default_income_account_for_item(obj): def get_serial_and_batch_bundle(child, parent, delivery_note_child=None): from erpnext.stock.serial_batch_bundle import SerialBatchCreation + if parent.get("is_return") and parent.get("packed_items"): + return + if child.get("use_serial_batch_fields"): return diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index c872bc92997..b1a7b89f4a4 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -2663,6 +2663,127 @@ class TestDeliveryNote(IntegrationTestCase): status = frappe.db.get_value("Serial No", row, "status") self.assertEqual(status, "Active") + def test_sales_return_for_product_bundle(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return + from erpnext.stock.doctype.item.test_item import make_item + + rm_items = [] + for item_code, properties in { + "_Packed Service Item": {"is_stock_item": 0}, + "_Packed FG Item New 1": { + "is_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "SN-PACKED-1-.#####", + }, + "_Packed FG Item New 2": { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BATCH-PACKED-2-.#####", + }, + "_Packed FG Item New 3": { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BATCH-PACKED-3-.#####", + "has_serial_no": 1, + "serial_no_series": "SN-PACKED-3-.#####", + }, + }.items(): + if not frappe.db.exists("Item", item_code): + make_item(item_code, properties) + + if item_code != "_Packed Service Item": + rm_items.append(item_code) + + for rate in [100, 200]: + make_stock_entry(item=item_code, target="_Test Warehouse - _TC", qty=5, rate=rate) + + make_product_bundle("_Packed Service Item", rm_items) + dn = create_delivery_note( + item_code="_Packed Service Item", + warehouse="_Test Warehouse - _TC", + qty=5, + ) + + serial_batch_map = {} + for row in dn.packed_items: + self.assertTrue(row.serial_and_batch_bundle) + if row.item_code not in serial_batch_map: + serial_batch_map[row.item_code] = frappe._dict( + { + "serial_nos": [], + "batches": defaultdict(int), + "serial_no_valuation": defaultdict(float), + "batch_no_valuation": defaultdict(float), + } + ) + + doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) + for entry in doc.entries: + if entry.serial_no: + serial_batch_map[row.item_code].serial_nos.append(entry.serial_no) + serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no] = entry.incoming_rate + if entry.batch_no: + serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty + serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no] = entry.incoming_rate + + dn1 = make_sales_return(dn.name) + dn1.items[0].qty = -2 + dn1.submit() + dn1.reload() + + for row in dn1.packed_items: + doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) + for entry in doc.entries: + if entry.serial_no: + self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos) + self.assertEqual( + entry.incoming_rate, + serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no], + ) + serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no) + serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no) + + elif entry.batch_no: + serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty + self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches) + self.assertEqual(entry.qty, 2.0) + self.assertEqual( + entry.incoming_rate, + serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no], + ) + + dn2 = make_sales_return(dn.name) + dn2.items[0].qty = -3 + dn2.submit() + dn2.reload() + + for row in dn2.packed_items: + doc = frappe.get_doc("Serial and Batch Bundle", row.serial_and_batch_bundle) + for entry in doc.entries: + if entry.serial_no: + self.assertTrue(entry.serial_no in serial_batch_map[row.item_code].serial_nos) + self.assertEqual( + entry.incoming_rate, + serial_batch_map[row.item_code].serial_no_valuation[entry.serial_no], + ) + serial_batch_map[row.item_code].serial_nos.remove(entry.serial_no) + serial_batch_map[row.item_code].serial_no_valuation.pop(entry.serial_no) + + elif entry.batch_no: + serial_batch_map[row.item_code].batches[entry.batch_no] += entry.qty + self.assertEqual(serial_batch_map[row.item_code].batches[entry.batch_no], 0.0) + + self.assertTrue(entry.batch_no in serial_batch_map[row.item_code].batches) + + self.assertEqual(entry.qty, 3.0) + self.assertEqual( + entry.incoming_rate, + serial_batch_map[row.item_code].batch_no_valuation[entry.batch_no], + ) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index ec2bfccd5a4..e7db3030008 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -639,7 +639,7 @@ class SerialandBatchBundle(Document): rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field) is_packed_item = False - if rate is None and child_table == "Delivery Note Item": + if rate is None and child_table in ["Delivery Note Item", "Sales Invoice Item"]: rate = frappe.db.get_value( "Packed Item", self.voucher_detail_no, From 959c3117959a4a75734ef61d85b734d323b8afe1 Mon Sep 17 00:00:00 2001 From: Murtaza Ghadiali Date: Tue, 30 Sep 2025 14:56:36 +0530 Subject: [PATCH 020/108] refactor: use naming series configuration for Sales Partner ID Replaced hardcoded ID assignment with Naming Series configuration so that Sales Partner IDs can be managed via Setup > Naming Series. Fixes #49623 --- erpnext/setup/doctype/sales_partner/sales_partner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.py b/erpnext/setup/doctype/sales_partner/sales_partner.py index 754e263c30a..36c24c0f37e 100644 --- a/erpnext/setup/doctype/sales_partner/sales_partner.py +++ b/erpnext/setup/doctype/sales_partner/sales_partner.py @@ -44,7 +44,7 @@ class SalesPartner(WebsiteGenerator): load_address_and_contact(self) def autoname(self): - self.name = self.partner_name + pass def validate(self): if not self.route: From 67f734172107772f4fb084acbc0f1c36967da083 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 9 Oct 2025 15:12:56 +0530 Subject: [PATCH 021/108] fix: call onload of buying controller in purchase_receipt.js --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 026965d8870..9c7035feafe 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -200,6 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend } onload() { + super.onload(); this.frm.set_query("supplier", function () { return { filters: { From f1f61ff61b907de50e7ae909c3f50b6e9044c002 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 9 Oct 2025 17:51:51 +0530 Subject: [PATCH 022/108] refactor: replace SQL query with Query Builder in fetch_items_with_pending_qty method --- erpnext/controllers/status_updater.py | 31 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index c366a4a7cf2..538887977d3 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -358,18 +358,25 @@ class StatusUpdater(Document): self.check_overflow_with_allowance(item, args) def fetch_items_with_pending_qty(self, args, item_field, items): - return frappe.db.sql( - """select name,`{item_code}` as item_code, `{target_ref_field}`, - `{target_field}`, parenttype, parent from `tab{target_dt}` - where `{target_ref_field}` < `{target_field}` - and name in %(names)s and docstatus=1""".format( - item_code=item_field, - target_ref_field=args["target_ref_field"], - target_field=args["target_field"], - target_dt=args["target_dt"], - ), - {"names": items}, - as_dict=1, + doctype = frappe.qb.DocType(args["target_dt"]) + item_field = doctype[item_field] + target_ref_field = doctype[args["target_ref_field"]] + target_field = doctype[args["target_field"]] + + return ( + frappe.qb.from_(doctype) + .select( + doctype.name, + item_field.as_("item_code"), + target_ref_field, + target_field, + doctype.parenttype, + doctype.parent, + ) + .where(target_ref_field < target_field) + .where(doctype.name.isin(items)) + .where(doctype.docstatus == 1) + .run(as_dict=True) ) def check_overflow_with_allowance(self, item, args): From 912ffc2d64a753b00101799634dda3bad4314007 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 9 Oct 2025 19:42:54 +0530 Subject: [PATCH 023/108] fix: consider negative qty in batch qty calculation --- erpnext/stock/doctype/batch/batch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 7a12844f1c6..246048cf5eb 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -158,7 +158,9 @@ class Batch(Document): @frappe.whitelist() def recalculate_batch_qty(self): - batches = get_batch_qty(batch_no=self.name, item_code=self.item, for_stock_levels=True) + batches = get_batch_qty( + batch_no=self.name, item_code=self.item, for_stock_levels=True, consider_negative_batches=True + ) batch_qty = 0.0 if batches: for row in batches: From 4a26810871db4b2427249fe44dc550d885d2a5ca Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 9 Oct 2025 20:47:00 +0530 Subject: [PATCH 024/108] fix: fix: incorrect PR status when using set landed cost based on PI rate --- .../purchase_invoice/test_purchase_invoice.py | 32 +++++++++++++++++++ .../purchase_receipt/purchase_receipt.py | 6 +++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 280d338d56d..cd63a3f757d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2629,6 +2629,38 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin): frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1) + @IntegrationTestCase.change_settings( + "Buying Settings", {"maintain_same_rate": 0, "set_landed_cost_based_on_purchase_invoice_rate": 1} + ) + def test_pr_status_rate_adjusted_from_pi(self): + pr = make_purchase_receipt(qty=5, rate=100) + pi = create_purchase_invoice_from_receipt(pr.name) + pi.submit() + pr.reload() + + # Inital check + self.assertEqual(pr.status, "Completed") + + pi.reload() + pi.cancel() + pi = create_purchase_invoice_from_receipt(pr.name) + pi.items[0].rate = 80 + pi.submit() + pr.reload() + + # Test 1 : Adjustment amount is negative + self.assertEqual(pr.status, "Completed") + + pi.reload() + pi.cancel() + pi = create_purchase_invoice_from_receipt(pr.name) + pi.items[0].rate = 120 + pi.submit() + pr.reload() + + # Test 2 : Adjustment amount is positive + self.assertEqual(pr.status, "Completed") + def test_opening_invoice_rounding_adjustment_validation(self): pi = make_purchase_invoice(do_not_save=1) pi.items[0].rate = 99.98 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 3e9ad4c5836..c512ca97eac 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1215,7 +1215,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate buying_settings = frappe.get_single("Buying Settings") over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance") - total_amount, total_billed_amount = 0, 0 + total_amount, total_billed_amount, pi_landed_cost_amount = 0, 0, 0 item_wise_returned_qty = get_item_wise_returned_qty(pr_doc) if adjust_incoming_rate: @@ -1255,6 +1255,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate ) * item.qty adjusted_amt = flt(adjusted_amt * flt(pr_doc.conversion_rate), item.precision("amount")) + pi_landed_cost_amount += adjusted_amt item.db_set("amount_difference_with_purchase_invoice", adjusted_amt, update_modified=False) elif amount and item.billed_amt > amount: per_over_billed = (flt(item.billed_amt / amount, 2) * 100) - 100 @@ -1265,6 +1266,9 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate ) ) + if pi_landed_cost_amount < 0: + total_billed_amount += abs(pi_landed_cost_amount) + percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6) pr_doc.db_set("per_billed", percent_billed) From b4cf6a1fb96b7e93bfcab27d8ada9083ad528353 Mon Sep 17 00:00:00 2001 From: Rehan Ansari Date: Thu, 9 Oct 2025 23:40:56 +0530 Subject: [PATCH 025/108] feat: add asset name to Asset Depreciations and Balances report --- .../asset_depreciations_and_balances.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index e2b989a4bea..839801d580d 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -354,7 +354,7 @@ def get_asset_details_for_grouped_by_category(filters): # nosemgrep return frappe.db.sql( f""" - SELECT a.name, + SELECT a.name, a.asset_name, ifnull(sum(case when a.purchase_date < %(from_date)s then case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then a.net_purchase_amount @@ -583,6 +583,14 @@ def get_columns(filters): "width": 120, } ) + columns.append( + { + "label": _("Asset Name"), + "fieldname": "asset_name", + "fieldtype": "Data", + "width": 140, + } + ) columns += [ { From 8d723d3da63594dff643c1372124f4875e9a7d55 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 10 Oct 2025 12:44:33 +0530 Subject: [PATCH 026/108] perf: Only check transaction deletion record once during req/job --- .../transaction_deletion_record/transaction_deletion_record.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 227df38e116..7f0e234a2e0 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -9,6 +9,7 @@ from frappe.desk.notifications import clear_notifications from frappe.model.document import Document from frappe.utils import cint, comma_and, create_batch, get_link_to_form from frappe.utils.background_jobs import get_job, is_job_enqueued +from frappe.utils.caching import request_cache LEDGER_ENTRY_DOCTYPES = frozenset( ( @@ -482,6 +483,7 @@ def get_doctypes_to_be_ignored(): @frappe.whitelist() +@request_cache def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None): if not company: return From 98f186b0e03ba07f4737f114d71b8c3f02b1394d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 10 Oct 2025 12:46:33 +0530 Subject: [PATCH 027/108] fix: posting date in serial no --- erpnext/patches/v16_0/update_serial_no_reference_name.py | 5 ++++- erpnext/stock/serial_batch_bundle.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v16_0/update_serial_no_reference_name.py b/erpnext/patches/v16_0/update_serial_no_reference_name.py index 967dcb22d04..b485db56c35 100644 --- a/erpnext/patches/v16_0/update_serial_no_reference_name.py +++ b/erpnext/patches/v16_0/update_serial_no_reference_name.py @@ -4,6 +4,9 @@ import frappe def execute(): # Update the reference_name, reference_doctype fields for Serial No where it is null + if not frappe.db.has_column("Serial and Batch Bundle", "posting_date"): + return + sabb = frappe.qb.DocType("Serial and Batch Bundle") sabb_entry = frappe.qb.DocType("Serial and Batch Entry") serial_no = frappe.qb.DocType("Serial No").as_("sn") @@ -16,7 +19,7 @@ def execute(): .on(sabb.name == sabb_entry.parent) .set(serial_no.reference_name, serial_no.purchase_document_no) .set(serial_no.reference_doctype, sabb.voucher_type) - .set(serial_no.posting_date, sabb.posting_datetime) + .set(serial_no.posting_date, sabb.posting_date) .where( (sabb.voucher_no == serial_no.purchase_document_no) & (sabb.is_cancelled == 0) diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index b895db99e40..2e9943578b4 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -1293,6 +1293,12 @@ class SerialBatchCreation: if self.get("voucher_type"): voucher_type = self.get("voucher_type") + posting_date = frappe.db.get_value( + voucher_type, + voucher_no, + "posting_date", + ) + for _i in range(abs(cint(self.actual_qty))): serial_no = make_autoname(self.serial_no_series, "Serial No") sr_nos.append(serial_no) @@ -1312,6 +1318,7 @@ class SerialBatchCreation: "Active", voucher_type, voucher_no, + posting_date, self.batch_no, ) ) @@ -1332,6 +1339,7 @@ class SerialBatchCreation: "status", "reference_doctype", "reference_name", + "posting_date", "batch_no", ] From 1f831d87834aeb455f6ecd612f4b4b7671a675f6 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Fri, 10 Oct 2025 13:31:57 +0530 Subject: [PATCH 028/108] fix: hide sales invoice creation for fully returned delivery notes --- erpnext/stock/doctype/delivery_note/delivery_note.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 0cd4e24ff93..527d672cc6a 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -334,6 +334,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends ( if ( doc.docstatus == 1 && !doc.is_return && + doc.per_returned != 100 && doc.status != "Closed" && flt(doc.per_billed) < 100 && frappe.model.can_create("Sales Invoice") From 4605051903e4077139aa406b69de43425a98f191 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 10 Oct 2025 15:21:17 +0530 Subject: [PATCH 029/108] feat: service expense account in the company --- .../test_repost_accounting_ledger.py | 2 +- erpnext/setup/doctype/company/company.js | 1 + erpnext/setup/doctype/company/company.json | 10 +++++++++- erpnext/setup/doctype/company/company.py | 16 ++++++++++++++++ erpnext/stock/get_item_details.py | 3 +++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py index 6dc1fd18ad1..cde007d14f6 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py @@ -232,7 +232,7 @@ class TestRepostAccountingLedger(AccountsTestMixin, IntegrationTestCase): company.save() test_cc = company.cost_center - default_expense_account = company.default_expense_account + default_expense_account = company.service_expense_account item = make_item(properties={"is_stock_item": 0}) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 1035fa3fb58..1018036c11b 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -289,6 +289,7 @@ erpnext.company.setup_queries = function (frm) { ["default_provisional_account", { root_type: ["in", ["Liability", "Asset"]] }], ["default_advance_received_account", { root_type: "Liability", account_type: "Receivable" }], ["default_advance_paid_account", { root_type: "Asset", account_type: "Payable" }], + ["service_expense_account", { root_type: "Expense" }], ], function (i, v) { erpnext.company.set_custom_query(frm, v); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 8d713600ba7..349fe5c0771 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -108,6 +108,7 @@ "transactions_annual_history", "purchase_expense_section", "purchase_expense_account", + "service_expense_account", "column_break_ereg", "purchase_expense_contra_account", "stock_tab", @@ -869,6 +870,13 @@ "fieldtype": "Link", "label": "Purchase Expense Contra Account", "options": "Account" + }, + { + "description": "For service item", + "fieldname": "service_expense_account", + "fieldtype": "Link", + "label": "Service Expense Account", + "options": "Account" } ], "icon": "fa fa-building", @@ -876,7 +884,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2025-10-01 17:34:10.971627", + "modified": "2025-10-10 15:12:37.941251", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 56f88c215ae..60788b98d2a 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -100,6 +100,7 @@ class Company(NestedSet): round_off_for_opening: DF.Link | None sales_monthly_history: DF.SmallText | None series_for_depreciation_entry: DF.Data | None + service_expense_account: DF.Link | None stock_adjustment_account: DF.Link | None stock_received_but_not_billed: DF.Link | None submit_err_jv: DF.Check @@ -570,6 +571,21 @@ class Company(NestedSet): self.db_set("disposal_account", disposal_acct) + if not self.service_expense_account: + service_expense_acct = frappe.db.get_value( + "Account", + { + "account_name": _("Marketing Expenses"), + "company": self.name, + "is_group": 0, + "root_type": "Expense", + }, + "name", + ) + + if service_expense_acct: + self.db_set("service_expense_account", service_expense_acct) + def _set_default_account(self, fieldname, account_type): if self.get(fieldname): return diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 22e0c69e8f4..2a8f144b790 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -502,6 +502,9 @@ def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> It } ) + if not item.is_stock_item and not out.expense_account: + out.expense_account = frappe.get_cached_value("Company", ctx.company, "service_expense_account") + default_supplier = get_default_supplier(ctx, item_defaults, item_group_defaults, brand_defaults) if default_supplier: out.supplier = default_supplier From 4b21c2cc4631bd63074f2a0f796160577a7d92d9 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Fri, 10 Oct 2025 18:47:15 +0530 Subject: [PATCH 030/108] fix(stock-reconciliation): include inventory dimensions in duplicate validation --- .../doctype/stock_reconciliation/stock_reconciliation.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 205fda5c41e..2969c502200 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -588,6 +588,10 @@ class StockReconciliation(StockController): if row.get(field): key.append(row.get(field)) + for dimension in get_inventory_dimensions(): + if row.get(dimension.get("fieldname")): + key.append(row.get(dimension.get("fieldname"))) + if key in item_warehouse_combinations: self.validation_messages.append( _get_msg(row_num, _("Same item and warehouse combination already entered.")) From 58203a89f13f8032aec15bfab5f78f99dea2e7ff Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Sat, 11 Oct 2025 12:27:51 +0530 Subject: [PATCH 031/108] fix(deferred revenue): validate service stop date --- erpnext/accounts/deferred_revenue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 0ccf997e66f..cb34b172ab4 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -46,7 +46,8 @@ def validate_service_stop_date(doc): if ( old_stop_dates and old_stop_dates.get(item.name) - and item.service_stop_date != old_stop_dates.get(item.name) + and item.service_stop_date + and getdate(item.service_stop_date) != getdate(old_stop_dates.get(item.name)) ): frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx)) From 8b6e58d02ab1a7553f5614e578fcf44de4b2d613 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 12 Oct 2025 11:11:50 +0530 Subject: [PATCH 032/108] fix: stock ledger adjustment entry --- erpnext/stock/stock_ledger.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 80d35ad3556..8b8fa1ad4d4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -892,9 +892,8 @@ class update_entries_after: sle.stock_value = self.wh_data.stock_value sle.stock_queue = json.dumps(self.wh_data.stock_queue) - if not sle.is_adjustment_entry: - sle.stock_value_difference = stock_value_difference - elif sle.is_adjustment_entry and not self.args.get("sle_id"): + sle.stock_value_difference = stock_value_difference + if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0: sle.stock_value_difference = ( get_stock_value_difference( sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no From 7fa800b87449fea05306f2ae31e8e03801e9ead8 Mon Sep 17 00:00:00 2001 From: thomasantony12 Date: Sun, 12 Oct 2025 11:18:03 +0530 Subject: [PATCH 033/108] fix: Batch ordering based on the method mentioned in settings --- erpnext/stock/doctype/batch/batch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index 52af15e158a..9dbc155fb46 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -239,6 +239,7 @@ def get_batch_qty( get_auto_batch_nos, ) + stock_settings = frappe.get_cached_doc("Stock Settings") batchwise_qty = defaultdict(float) kwargs = frappe._dict( { @@ -247,6 +248,7 @@ def get_batch_qty( "posting_date": posting_date, "posting_time": posting_time, "batch_no": batch_no, + "based_on": stock_settings.pick_serial_and_batch_based_on, "ignore_voucher_nos": ignore_voucher_nos, "for_stock_levels": for_stock_levels, "consider_negative_batches": consider_negative_batches, From e85238383f714e0e5a75341b51768fbfce110258 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 9 Sep 2025 12:55:55 +0530 Subject: [PATCH 034/108] feat: default print format for sales invoice --- .../sales_invoice_print_format/__init__.py | 0 .../sales_invoice_print_format.html | 148 ++++++++++++++++++ .../sales_invoice_print_format.json | 32 ++++ 3 files changed, 180 insertions(+) create mode 100644 erpnext/accounts/print_format/sales_invoice_print_format/__init__.py create mode 100644 erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html create mode 100644 erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/__init__.py b/erpnext/accounts/print_format/sales_invoice_print_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html new file mode 100644 index 00000000000..c5800c0645f --- /dev/null +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -0,0 +1,148 @@ +{%- 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 %} +
{{ letter_head }}
+{% endif %} {% if print_heading_template %} {{ frappe.render_template(print_heading_template, {"doc":doc}) }} +{% else %} {% endif %} {%- if doc.meta.is_submittable and doc.docstatus==2-%} +
+

{{ _("CANCELLED") }}

+
+{%- endif -%} {%- endmacro -%} {% for page in layout %} +
+
+ {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }} +
+ + + + + + + + + + + + {% set address = (doc.address_display) %} {% if address.rstrip().endswith("
") %} {% set + address = address[:-5] %} {% endif %} {% set company_address_display = + (doc.company_address_display) %} {% if company_address_display.rstrip().endswith("
") %} {% + set company_address_display = company_address_display[:-5] %} {% endif %} + + + +
+ {{ _("Customer Name") }}: {{doc.customer_name }} + + {{ _("Payment Due Date") }}: {{ + frappe.utils.format_date(doc.due_date) }} +
{{ _("Invoice Number") }}: {{ doc.name }} + {{ _("Invoice Date") }}: {{ + frappe.utils.format_date(doc.posting_date) }} +
{{ _("Bill From") }}:
{{ address }}
{{ _("Bill To") }}:
{{ company_address_display }}
+ + + + + + + + + + + + + + + + + + + + + {% for item in doc.items %} + + + + + + + + {% endfor %} + +
{{ _("No") }}{{ _("Item") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }}
{{ loop.index }}{{ item.item_name }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }}
+ + +
+
+
+

{{ _("Total in words") }}

+
{{ doc.in_words }}
+
+
+
+ + + + + + + + + + {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if + tax_rate > 0 %} + + + + + {% endif %} + + + + +
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
+ {{ _("Discount") }} ({{ doc.additional_discount_percentage }}%): + {{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%):{{ doc.get_formatted("total_taxes_and_charges", doc) }}
{{ _("Grand Total:") }}{{ doc.get_formatted("grand_total", doc) }}
+
+
+ + + {% if doc.taxes_and_charges %} +
{{ _("Tax Breakup") }}
+
+ + + + + + {% for row in doc.taxes %} + + {% endfor %} + + + + {% for item in doc.items %} + + + + {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %} + + {% endfor %} + + {% endfor %} + +
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }}
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} + ({{ (row.rate) }}%) {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) + }} +
+
+ {% endif %} + + + {% if doc.terms %} +
+
{{ _("Terms and Conditions") }}
+ {{ doc.terms}} +
+ {% endif %} +
+{% endfor %} diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json new file mode 100644 index 00000000000..34198f03e9c --- /dev/null +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.json @@ -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" +} From af974fbccdf3ce8e473e59cbed924ada5e72107a Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 9 Sep 2025 12:56:51 +0530 Subject: [PATCH 035/108] feat: add css --- .../sales_invoice_print_format.html | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html index c5800c0645f..c9fbea3698b 100644 --- a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -12,6 +12,147 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %} {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
+ +
From 156dda8157dabf4b68dedd1fa29f0576863fd345 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 9 Sep 2025 13:25:14 +0530 Subject: [PATCH 036/108] style: change padding --- .../sales_invoice_print_format/sales_invoice_print_format.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html index c9fbea3698b..65bfd93c2be 100644 --- a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -16,7 +16,7 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %} .invoice-wrapper { font-family: "Inter", sans-serif; color: var(--black-overlay-700); - padding: 24px; + padding: 12px; } table.info-table, From 7386270fce5cb93e096c0a25ddac378c9c56236d Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 9 Sep 2025 14:45:37 +0530 Subject: [PATCH 037/108] feat: add letterhead fixture --- erpnext/fixtures/letter_head.json | 48 +++++++++++++++++++++++++++++++ erpnext/hooks.py | 2 ++ 2 files changed, 50 insertions(+) create mode 100644 erpnext/fixtures/letter_head.json diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json new file mode 100644 index 00000000000..4b9e7d9cc46 --- /dev/null +++ b/erpnext/fixtures/letter_head.json @@ -0,0 +1,48 @@ +[ + { + "align": "Left", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"logo_for_printing\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n\n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n", + "disabled": 0, + "docstatus": 0, + "doctype": "Letter Head", + "footer": null, + "footer_align": "Left", + "footer_image": null, + "footer_image_height": 0.0, + "footer_image_width": 0.0, + "footer_script": null, + "footer_source": "HTML", + "header_script": null, + "image": null, + "image_height": 0.0, + "image_width": 0.0, + "is_default": 0, + "letter_head_name": "Sales Invoice Letter Head", + "modified": "2025-09-09 14:31:07.209406", + "name": "Sales Invoice Letter Head", + "source": "HTML" + }, + { + "align": "Left", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"logo_for_printing\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n
\n", + "disabled": 0, + "docstatus": 0, + "doctype": "Letter Head", + "footer": null, + "footer_align": "Left", + "footer_image": null, + "footer_image_height": 0.0, + "footer_image_width": 0.0, + "footer_script": null, + "footer_source": "HTML", + "header_script": null, + "image": null, + "image_height": 0.0, + "image_width": 0.0, + "is_default": 1, + "letter_head_name": "Print Format Letter Head", + "modified": "2025-09-09 14:31:07.204974", + "name": "Print Format Letter Head", + "source": "HTML" + } +] \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 22bff524dea..540fd6c36c4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -600,6 +600,8 @@ user_privacy_documents = [ }, ] +fixtures = ["Letter Head"] + # ERPNext doctypes for Global Search global_search_doctypes = { "Default": [ From 1adbf90d8c51c44035b48d23f55ab8109eb43deb Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 9 Sep 2025 18:58:45 +0530 Subject: [PATCH 038/108] fix: css changes in letterhead --- .../sales_invoice_print_format.html | 4 ++-- erpnext/fixtures/letter_head.json | 8 ++++---- erpnext/hooks.py | 8 +++++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html index 65bfd93c2be..6a9b5f24564 100644 --- a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -148,12 +148,12 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %} font-style: normal; font-weight: 420; line-height: 21px; - padding: 12px; + padding: 5px; letter-spacing: 0.14px; } - +
- {% set address = (doc.address_display) %} {% if address.rstrip().endswith("
") %} {% set + {% set address = (doc.address_display) %} {% if address and address.rstrip().endswith("
") %} {% set address = address[:-5] %} {% endif %} {% set company_address_display = - (doc.company_address_display) %} {% if company_address_display.rstrip().endswith("
") %} {% + (doc.company_address_display) %} {% if company_address_display and company_address_display.rstrip().endswith("
") %} {% set company_address_display = company_address_display[:-5] %} {% endif %} From c78079628462ab9520e9e631c7e6ec1b9e653589 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 15 Sep 2025 16:27:12 +0530 Subject: [PATCH 042/108] fix: remove border if company logo not available --- erpnext/fixtures/letter_head.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 36314aa4317..1b0eb0962c8 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -16,15 +16,15 @@ "image": null, "image_height": 0.0, "image_width": 0.0, - "is_default": 1, + "is_default": 0, "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-10 14:21:04.051369", + "modified": "2025-09-15 16:23:58.583661", "name": "Sales Invoice Letter Head", "source": "HTML" }, { "align": "Left", - "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -41,7 +41,7 @@ "image_width": 0.0, "is_default": 0, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-10 14:21:04.063016", + "modified": "2025-09-15 16:14:24.080812", "name": "Print Format Letter Head", "source": "HTML" } From 4cc2afbd832aab3dfc3ac5dc668934411d79daad Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 15 Sep 2025 16:34:14 +0530 Subject: [PATCH 043/108] feat: print format design two --- .../print_format_sales_invoice/__init__.py | 0 .../print_format_sales_invoice.html | 310 ++++++++++++++++++ .../print_format_sales_invoice.json | 32 ++ 3 files changed, 342 insertions(+) create mode 100644 erpnext/accounts/print_format/print_format_sales_invoice/__init__.py create mode 100644 erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html create mode 100644 erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/__init__.py b/erpnext/accounts/print_format/print_format_sales_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html new file mode 100644 index 00000000000..aa1f9923326 --- /dev/null +++ b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html @@ -0,0 +1,310 @@ +{%- 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 %} +
{{ letter_head }}
+{% endif %} {% if print_heading_template %} {{ frappe.render_template(print_heading_template, {"doc":doc}) }} +{% endif %} {%- if doc.meta.is_submittable and doc.docstatus==2 -%} +
+

{{ _("CANCELLED") }}

+
+{%- endif -%} {%- endmacro -%} {% for page in layout %} +
+
+ {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }} +
+ + + +
{{ _("Customer Name") }}: {{doc.customer_name }} diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 4b9e7d9cc46..b956a8c64e3 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"logo_for_printing\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n\n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n\n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -18,13 +18,13 @@ "image_width": 0.0, "is_default": 0, "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-09 14:31:07.209406", + "modified": "2025-09-09 18:25:09.704061", "name": "Sales Invoice Letter Head", "source": "HTML" }, { "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"logo_for_printing\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n
\n", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -41,7 +41,7 @@ "image_width": 0.0, "is_default": 1, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-09 14:31:07.204974", + "modified": "2025-09-09 18:11:28.309162", "name": "Print Format Letter Head", "source": "HTML" } diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 540fd6c36c4..213efd9a0f6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -600,7 +600,13 @@ user_privacy_documents = [ }, ] -fixtures = ["Letter Head"] +fixtures = [ + { + "dt": "Letter Head", + "filters": [["name", "in", ["Sales Invoice Letter Head", "Print Format Letter Head"]]], + } +] + # ERPNext doctypes for Global Search global_search_doctypes = { From 2bc19783cb61abb2e56c9ad134667b5d77fe36e2 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Sep 2025 13:24:30 +0530 Subject: [PATCH 039/108] fix: letterhead styling --- erpnext/fixtures/letter_head.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index b956a8c64e3..32d56e77475 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n\n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -18,13 +18,13 @@ "image_width": 0.0, "is_default": 0, "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-09 18:25:09.704061", + "modified": "2025-09-10 00:56:32.108422", "name": "Sales Invoice Letter Head", "source": "HTML" }, { "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n {% set pan = frappe.db.get_value(\"Company\", doc.company, \"pan\") %}\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n {% if pan %}\n
\n
{{ _(\"PAN:\") }}
\n
{{ pan }}

\n
\n {% endif %}\n
\n
\n
\n", + "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -41,7 +41,7 @@ "image_width": 0.0, "is_default": 1, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-09 18:11:28.309162", + "modified": "2025-09-10 00:56:32.101771", "name": "Print Format Letter Head", "source": "HTML" } From b5c739d1cc0d58fa654df19dede4fe7d3b18fc89 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Sep 2025 14:21:50 +0530 Subject: [PATCH 040/108] fix: broken img tag in letterhead --- erpnext/fixtures/letter_head.json | 50 +++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 32d56e77475..36314aa4317 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,30 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", - "disabled": 0, - "docstatus": 0, - "doctype": "Letter Head", - "footer": null, - "footer_align": "Left", - "footer_image": null, - "footer_image_height": 0.0, - "footer_image_width": 0.0, - "footer_script": null, - "footer_source": "HTML", - "header_script": null, - "image": null, - "image_height": 0.0, - "image_width": 0.0, - "is_default": 0, - "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-10 00:56:32.108422", - "name": "Sales Invoice Letter Head", - "source": "HTML" - }, - { - "align": "Left", - "content": "\n\n
\n
\n
\n {% if frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -40,8 +17,31 @@ "image_height": 0.0, "image_width": 0.0, "is_default": 1, + "letter_head_name": "Sales Invoice Letter Head", + "modified": "2025-09-10 14:21:04.051369", + "name": "Sales Invoice Letter Head", + "source": "HTML" + }, + { + "align": "Left", + "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n
\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "disabled": 0, + "docstatus": 0, + "doctype": "Letter Head", + "footer": null, + "footer_align": "Left", + "footer_image": null, + "footer_image_height": 0.0, + "footer_image_width": 0.0, + "footer_script": null, + "footer_source": "HTML", + "header_script": null, + "image": null, + "image_height": 0.0, + "image_width": 0.0, + "is_default": 0, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-10 00:56:32.101771", + "modified": "2025-09-10 14:21:04.063016", "name": "Print Format Letter Head", "source": "HTML" } From ddf4a83cf86edc74f167fe24b463e54237b75ba7 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 15 Sep 2025 16:22:39 +0530 Subject: [PATCH 041/108] fix: condition based address display --- .../sales_invoice_print_format.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html index 6a9b5f24564..a97d5f67790 100644 --- a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -171,9 +171,9 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %}
{{ _("Bill From") }}:
{{ address }}
{{ _("Bill To") }}:
{{ company_address_display }}
+ + + + + +
+
+
Customer Name:
+
Bill to:
+
+
+
{{ doc.customer_name }}
+
{{ doc.address_display or "" }}
+
+
+
+
Invoice Number:
+
Invoice Date:
+
Payment Due Date:
+
+
+
{{ doc.name }}
+
{{ frappe.utils.format_date(doc.posting_date) }}
+
{{ frappe.utils.format_date(doc.due_date) }}
+
+
+ + + + + + + + + + + + + + + + + + + + + + + {% for item in doc.items %} + + + + + + + + + + {% endfor %} + +
{{ _("No") }}{{ _("Item") }}{{ _("HSN/SAC") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }}
{{ loop.index }} +
+
+ {% if item.image %} +
+ {{ item.item_name }} +
+ {% endif %} +
+
{{ item.item_name }}
+
+
{{ item.gst_hsn_code }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }}
+ + + + + + + + + + + +
  + + + + + + + + + + {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if + tax_rate > 0 %} + + + + + {% endif %} +
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
+ {{ _("Discount") }} ({{ doc.additional_discount_percentage or 0 }}%): + {{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%):{{ doc.get_formatted("total_taxes_and_charges", doc) }}
+
+
+ + + + + + +
+
{{ _("In Words: ") }}{{ doc.in_words }}
+
{{ _("Grand Total:") }} + + {{ doc.get_formatted("grand_total", doc) }} + +
+
+
+ + {% if doc.taxes_and_charges %} + +
{{ _("Tax Breakup") }}
+ + + + + {% for row in doc.taxes %} + + {% endfor %} + + + + {% for item in doc.items %} + + + + {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %} + + {% endfor %} + + {% endfor %} + +
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }} ({{ row.rate }}%)
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} + {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) }} +
+ {% endif %} + + + {% if doc.terms %} +
+
{{ _("Terms and Conditions") }}
+ {{ doc.terms}} +
+ {% endif %} + +{% endfor %} diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json new file mode 100644 index 00000000000..9952485cc28 --- /dev/null +++ b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.json @@ -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" +} From e08f82909c371d6a50e18c73218525a010000a0a Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Mon, 15 Sep 2025 16:39:33 +0530 Subject: [PATCH 044/108] fix: radius of the items/tax table thead --- .../print_format_sales_invoice/print_format_sales_invoice.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html index aa1f9923326..233083b1f34 100644 --- a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html +++ b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html @@ -86,10 +86,12 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %} .items-table tr:first-child td:first-child, .tax-table tr:first-child td:first-child { border-top-left-radius: 10px; + border-bottom-left-radius: 10px; } .items-table tr:first-child td:last-child, .tax-table tr:first-child td:last-child { border-top-right-radius: 10px; + border-bottom-right-radius: 10px; } .in-words { From 6b83309750eec9d51a506a4ebb9094ef08b7aba8 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 16 Sep 2025 22:45:05 +0530 Subject: [PATCH 045/108] test: just debugging --- .../test_process_statement_of_accounts.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 4f9dd9a590d..5bf0c0b4464 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,7 +81,8 @@ 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) + if not frappe.flags.in_migrate: + send_emails(process_soa.name, from_scheduler=True) process_soa.load_from_db() self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7))) From 17397ae652c6a47f5f8ba489f55b257e001f95bc Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 16 Sep 2025 23:09:42 +0530 Subject: [PATCH 046/108] test: add in_install condition for debugging --- .../test_process_statement_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5bf0c0b4464..acea3614c5e 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,7 +81,7 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase): process_soa = create_process_soa( name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable" ) - if not frappe.flags.in_migrate: + if not frappe.flags.in_migrate and not frappe.flags.in_install: send_emails(process_soa.name, from_scheduler=True) process_soa.load_from_db() self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7))) From f1a2e1b7258c161b20f56a3e00f7b388c6d473fb Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 16 Sep 2025 23:45:41 +0530 Subject: [PATCH 047/108] refactor: remove img tag for testing --- erpnext/fixtures/letter_head.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 1b0eb0962c8..7c77cb5d9fe 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -24,7 +24,7 @@ }, { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", From 5f97bec2b32ed0da7403e42dfce8f1c9c6575e70 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 17 Sep 2025 01:02:51 +0530 Subject: [PATCH 048/108] refactor: revert debugging changes --- .../test_process_statement_of_accounts.py | 4 ++-- erpnext/fixtures/letter_head.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 acea3614c5e..7d5cfb90af8 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,8 +81,8 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase): process_soa = create_process_soa( name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable" ) - if not frappe.flags.in_migrate and not frappe.flags.in_install: - send_emails(process_soa.name, from_scheduler=True) + + 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/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 7c77cb5d9fe..1b0eb0962c8 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -24,7 +24,7 @@ }, { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", From 5c4f7782231bbbf45147cf4999a58c9dbd5ad934 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 17 Sep 2025 12:35:55 +0530 Subject: [PATCH 049/108] style: center-align logo within its container div --- erpnext/fixtures/letter_head.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 1b0eb0962c8..41ab9ccd47c 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -16,15 +16,15 @@ "image": null, "image_height": 0.0, "image_width": 0.0, - "is_default": 0, + "is_default": 1, "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-15 16:23:58.583661", + "modified": "2025-09-17 11:58:06.023854", "name": "Sales Invoice Letter Head", "source": "HTML" }, { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -41,7 +41,7 @@ "image_width": 0.0, "is_default": 0, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-15 16:14:24.080812", + "modified": "2025-09-17 12:10:40.229447", "name": "Print Format Letter Head", "source": "HTML" } From f4f2d11fa47fbe774f89767bed43a34bab9e56df Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 17 Sep 2025 12:37:25 +0530 Subject: [PATCH 050/108] style: always show border even when logo is missing --- erpnext/fixtures/letter_head.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index 41ab9ccd47c..f5bb820fc38 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -1,7 +1,7 @@ [ { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", + "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n \n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n
\n
\n
{{ _(\"Invoice:\") }}
\n
{{ doc.name }}

\n
\n
\n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -18,13 +18,13 @@ "image_width": 0.0, "is_default": 1, "letter_head_name": "Sales Invoice Letter Head", - "modified": "2025-09-17 11:58:06.023854", + "modified": "2025-09-17 12:31:46.314509", "name": "Sales Invoice Letter Head", "source": "HTML" }, { "align": "Left", - "content": "\n\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n
\n \n
\n {% endif %}\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", + "content": "\n\n
\n
\n
\n {% set company_logo = frappe.db.get_value(\"Company\", doc.company, \"company_logo\") %}\n {% if company_logo %}\n \n {% endif %}\n
\n
\n
{{ doc.company }}
\n \n {% set company_address = frappe.get_all(\n \"Dynamic Link\",\n fields=[\"parent\"],\n filters={\n \"link_doctype\": \"Company\",\n \"link_name\": doc.company,\n \"parenttype\": \"Address\"\n },\n limit=1\n ) %}\n \n {% if company_address %}\n {% set address = frappe.get_doc(\"Address\", company_address[0].parent) %}\n {{ address.address_line1 or \"\" }}
\n {% if address.address_line2 %}{{ address.address_line2 }}
{% endif %}\n {{ address.city or \"\" }} {{ address.state or \"\" }} {{ address.pincode or \"\" }} {{ address.country or \"\" }}
\n {% endif %}\n
\n
\n
\n
\n \n
\n
\n {{ _(\"Sales Invoice\") }}\n
\n
\n {{ doc.name }}\n
\n
\n\n
\n {% set website = frappe.db.get_value(\"Company\", doc.company, \"website\") %}\n {% set email = frappe.db.get_value(\"Company\", doc.company, \"email\") %}\n {% set phone_no = frappe.db.get_value(\"Company\", doc.company, \"phone_no\") %}\n \n \n {% if website %}\n
\n
{{ _(\"Website:\") }}
\n
{{ website }}

\n
\n {% endif %}\n {% if email %}\n
\n
{{ _(\"Email:\") }}
\n
{{ email }}

\n
\n {% endif %}\n {% if phone_no %}\n
\n
{{ _(\"Contact:\") }}
\n
{{ phone_no }}

\n
\n {% endif %}\n \n
\n
\n
\n", "disabled": 0, "docstatus": 0, "doctype": "Letter Head", @@ -41,7 +41,7 @@ "image_width": 0.0, "is_default": 0, "letter_head_name": "Print Format Letter Head", - "modified": "2025-09-17 12:10:40.229447", + "modified": "2025-09-17 12:31:46.318339", "name": "Print Format Letter Head", "source": "HTML" } From 670361059687c0a31bfb08fc7054b46d0af64e66 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 17 Sep 2025 13:14:56 +0530 Subject: [PATCH 051/108] fix: do not make letterhead default --- erpnext/fixtures/letter_head.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/fixtures/letter_head.json b/erpnext/fixtures/letter_head.json index f5bb820fc38..48d46ea937b 100644 --- a/erpnext/fixtures/letter_head.json +++ b/erpnext/fixtures/letter_head.json @@ -16,7 +16,7 @@ "image": null, "image_height": 0.0, "image_width": 0.0, - "is_default": 1, + "is_default": 0, "letter_head_name": "Sales Invoice Letter Head", "modified": "2025-09-17 12:31:46.314509", "name": "Sales Invoice Letter Head", From 39b6aab714fb9155dd4227c0441267a13ac3dfe8 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Fri, 19 Sep 2025 02:14:32 +0530 Subject: [PATCH 052/108] fix: update styles to work with wkhtmltopdf rendering --- .../print_format_sales_invoice.html | 355 ++++++------ .../sales_invoice_print_format.html | 504 +++++++++--------- 2 files changed, 456 insertions(+), 403 deletions(-) diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html index 233083b1f34..392b3af0dc1 100644 --- a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html +++ b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html @@ -2,23 +2,26 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %}
{{ letter_head }}
{% endif %} {% if print_heading_template %} {{ frappe.render_template(print_heading_template, {"doc":doc}) }} -{% endif %} {%- if doc.meta.is_submittable and doc.docstatus==2 -%} -
-

{{ _("CANCELLED") }}

-
-{%- endif -%} {%- endmacro -%} {% for page in layout %} +{% endif %} {%- endmacro -%} {% for page in layout %}
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
- - - - - - -
-
-
Customer Name:
-
Bill to:
-
-
-
{{ doc.customer_name }}
-
{{ doc.address_display or "" }}
-
-
-
-
Invoice Number:
-
Invoice Date:
-
Payment Due Date:
-
-
-
{{ doc.name }}
-
{{ frappe.utils.format_date(doc.posting_date) }}
-
{{ frappe.utils.format_date(doc.due_date) }}
-
-
- - - - - - - - - - - - +
- - - - - - - - - - {% for item in doc.items %} - - - - - - - + - {% endfor %} - -
{{ _("No") }}{{ _("Item") }}{{ _("HSN/SAC") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }}
{{ loop.index }} -
-
- {% if item.image %} -
- {{ item.item_name }} -
- {% endif %} -
-
{{ item.item_name }}
+
+
+
Customer Name:
+
Bill to:
+
+
+
{{ doc.customer_name }}
+
{{ doc.address_display or "" }}
{{ item.gst_hsn_code }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }} +
+
Invoice Number:
+
Invoice Date:
+
Payment Due Date:
+
+
+
{{ doc.name }}
+
{{ frappe.utils.format_date(doc.posting_date) }}
+
{{ frappe.utils.format_date(doc.due_date) }}
+
+
+
- - - + +
 
+ + + + + + + + + + + + + + + + + + + + {% for item in doc.items %} + + + - - + + + + + + {% endfor %} + +
{{ _("No") }}{{ _("Item") }}{{ _("HSN/SAC") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }}
{{ loop.index }} +
+
+ {% if item.image %} +
+ {{ item.item_name }} +
+ {% endif %} +
+
{{ item.item_name }}
+
+
- - - - - - - - - - {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if - tax_rate > 0 %} - - - - - {% endif %} -
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
- {{ _("Discount") }} ({{ doc.additional_discount_percentage or 0 }}%): - {{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%):{{ doc.get_formatted("total_taxes_and_charges", doc) }}
-
{{ item.gst_hsn_code }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }}
- - -
+ + + + + - -
  - - + + + + + - + + + {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% + if tax_rate > 0 %} + + + {% endif %}
-
{{ _("In Words: ") }}{{ doc.in_words }}
+
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
+ {{ _("Discount") }} ({{ doc.additional_discount_percentage or 0 }}%): {{ _("Grand Total:") }}{{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%): - - {{ doc.get_formatted("grand_total", doc) }} - + {{ doc.get_formatted("total_taxes_and_charges", doc) }}
- -
- - {% if doc.taxes_and_charges %} - -
{{ _("Tax Breakup") }}
- - - - - {% for row in doc.taxes %} - - {% endfor %} - - - - {% for item in doc.items %} - - - - {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %} - - {% endfor %} - {% endfor %} - -
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }} ({{ row.rate }}%)
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} - {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) }}
- {% endif %} - - {% if doc.terms %} -
-
{{ _("Terms and Conditions") }}
- {{ doc.terms}} + + +
+ + + + + + +
+
+ {{ _("In Words: ") }}{{ doc.in_words }} +
+
{{ _("Grand Total:") }} + + {{ doc.get_formatted("grand_total", doc) }} + +
+
+ + + + + {% if doc.taxes_and_charges %} + +
{{ _("Tax Breakup") }}
+ + + + + {% for row in doc.taxes %} + + {% endfor %} + + + + {% for item in doc.items %} + + + + {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %} + + {% endfor %} + + {% endfor %} + +
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }} ({{ row.rate }}%)
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} + {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) }} +
+ {% endif %} + + + {% if doc.terms %} +
+
{{ _("Terms and Conditions") }}
+ {{ doc.terms}} +
+ {% endif %}
- {% endif %}
{% endfor %} diff --git a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html index a97d5f67790..4457b2ac7a6 100644 --- a/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html +++ b/erpnext/accounts/print_format/sales_invoice_print_format/sales_invoice_print_format.html @@ -12,278 +12,304 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %} {{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
- + .title { + color: #7c7c7c !important; + } - - - - - - - - - - - {% set address = (doc.address_display) %} {% if address and address.rstrip().endswith("
") %} {% set - address = address[:-5] %} {% endif %} {% set company_address_display = - (doc.company_address_display) %} {% if company_address_display and company_address_display.rstrip().endswith("
") %} {% - set company_address_display = company_address_display[:-5] %} {% endif %} - - - -
- {{ _("Customer Name") }}: {{doc.customer_name }} - - {{ _("Payment Due Date") }}: {{ - frappe.utils.format_date(doc.due_date) }} -
{{ _("Invoice Number") }}: {{ doc.name }} - {{ _("Invoice Date") }}: {{ - frappe.utils.format_date(doc.posting_date) }} -
{{ _("Bill From") }}:
{{ address }}
{{ _("Bill To") }}:
{{ company_address_display }}
+ .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; + } + +
- - - - - + + - - - {% for item in doc.items %} - - - - - + + - {% endfor %} - -
{{ _("No") }}{{ _("Item") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }} + {{ _("Customer Name") }}: {{doc.customer_name }} + + {{ _("Payment Due Date") }}: {{ + frappe.utils.format_date(doc.due_date) }} +
{{ loop.index }}{{ item.item_name }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }}{{ item.get_formatted("net_amount", doc) }}{{ _("Invoice Number") }}: {{ doc.name }} + {{ _("Invoice Date") }}: {{ + frappe.utils.format_date(doc.posting_date) }} +
+ + {% set address = (doc.address_display) %} {% if address and address.rstrip().endswith("
") + %} {% set address = address[:-5] %} {% endif %} {% set company_address_display = + (doc.company_address_display) %} {% if company_address_display and + company_address_display.rstrip().endswith("
") %} {% set company_address_display = + company_address_display[:-5] %} {% endif %} + {{ _("Bill From") }}:
{{ address }} + {{ _("Bill To") }}:
{{ company_address_display }} + + - -
-
-
-

{{ _("Total in words") }}

-
{{ doc.in_words }}
-
-
-
- - - - - - - - - - {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if - tax_rate > 0 %} - - - - - {% endif %} - - - - -
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
- {{ _("Discount") }} ({{ doc.additional_discount_percentage }}%): - {{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%):{{ doc.get_formatted("total_taxes_and_charges", doc) }}
{{ _("Grand Total:") }}{{ doc.get_formatted("grand_total", doc) }}
-
-
- - - {% if doc.taxes_and_charges %} -
{{ _("Tax Breakup") }}
-
- + +
+ + + + + + + - - - {% for row in doc.taxes %} - - {% endfor %} + + + + + {% for item in doc.items %} - - - {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) %} - + + + + - {% endfor %} {% endfor %}
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }}{{ _("No") }}{{ _("Item") }}{{ _("Quantity") }}{{ _("Rate") }}{{ _("Amount") }}
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} - ({{ (row.rate) }}%) {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, doc) - }} + {{ loop.index }}{{ item.item_name }}{{ item.get_formatted("qty", 0) }} {{ item.uom }}{{ item.get_formatted("net_rate", doc) }} + {{ item.get_formatted("net_amount", doc) }}
-
- {% endif %} - - {% if doc.terms %} -
-
{{ _("Terms and Conditions") }}
- {{ doc.terms}} + +
+
+
+

{{ _("Total in words") }}

+
{{ doc.in_words }}
+
+
+
+ + + + + + + + + + {% set tax_rate = doc.taxes | selectattr("tax_amount") | sum(attribute="rate") %} {% if + tax_rate > 0 %} + + + + + {% endif %} + + + + +
{{ _("Sub Total:") }}{{ doc.get_formatted("total", doc) }}
+ {{ _("Discount") }} ({{ doc.additional_discount_percentage }}%): + {{ doc.get_formatted("discount_amount", doc) }}
{{ _("Tax") }} ({{ tax_rate }}%):{{ doc.get_formatted("total_taxes_and_charges", doc) }}
{{ _("Grand Total:") }}{{ doc.get_formatted("grand_total", doc) }}
+
+
+ + + {% if doc.taxes_and_charges %} +
{{ _("Tax Breakup") }}
+
+ + + + + + {% for row in doc.taxes %} + + {% endfor %} + + + + {% for item in doc.items %} + + + + {% for row in doc.taxes %} {% set tax_amount = item.base_net_amount * (row.rate / 100) + %} + + {% endfor %} + + {% endfor %} + +
{{ _("HSN/SAC") }}{{ _("Taxable Amount") }}{{ row.description }}
{{ item.gst_hsn_code }}{{ item.get_formatted("base_net_amount", doc) }} + ({{ (row.rate) }}%) {{ frappe.format_value(tax_amount, {"fieldtype": "Currency"}, + doc) }} +
+
+ {% endif %} + + + {% if doc.terms %} +
+
{{ _("Terms and Conditions") }}
+ {{ doc.terms}} +
+ {% endif %}
- {% endif %}
{% endfor %} From e22373192491a11ec4d8904d40b256fb51ebfe94 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Fri, 19 Sep 2025 14:09:18 +0530 Subject: [PATCH 053/108] refactor: remove flex usage for better wkhtmltopdf support --- .../print_format_sales_invoice.html | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html index 392b3af0dc1..2042b4271ba 100644 --- a/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html +++ b/erpnext/accounts/print_format/print_format_sales_invoice/print_format_sales_invoice.html @@ -1,8 +1,13 @@ {%- 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 %} -
{{ letter_head }}
-{% endif %} {% if print_heading_template %} {{ frappe.render_template(print_heading_template, {"doc":doc}) }} -{% endif %} {%- endmacro -%} {% for page in layout %} +print_heading_template=None) -%} +{% if letter_head and not no_letterhead %} +
{{ letter_head }}
+{% endif %} +{% if print_heading_template %} + {{ frappe.render_template(print_heading_template, {"doc":doc}) }} +{% endif %} +{%- endmacro -%} +{% for page in layout %}
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }} @@ -11,7 +16,7 @@ print_heading_template=None) -%} {% if letter_head and not no_letterhead %}