From 376da8df0a20971caf1800d5d6bbdeece88af2cd Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:52:10 +0100 Subject: [PATCH 01/38] feat(Item Price): validate UOM (cherry picked from commit 69824eff80eb70b7e3139c6db84eedad84ceb8b4) # Conflicts: # erpnext/stock/doctype/item_price/item_price.py --- erpnext/stock/doctype/item_price/item_price.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 5445e1b88b0..63cb604a28d 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -55,6 +55,19 @@ class ItemPrice(Document): if not frappe.db.exists("Item", self.item_code): frappe.throw(_("Item {0} not found.").format(self.item_code)) +<<<<<<< HEAD +======= + if self.uom and not frappe.db.exists( + "UOM Conversion Detail", {"parenttype": "Item", "parent": self.item_code, "uom": self.uom} + ): + frappe.throw(_("UOM {0} not found in Item {1}").format(self.uom, self.item_code)) + + def validate_dates(self): + if self.valid_from and self.valid_upto: + if getdate(self.valid_from) > getdate(self.valid_upto): + frappe.throw(_("Valid From Date must be lesser than Valid Up To Date.")) + +>>>>>>> 69824eff80 (feat(Item Price): validate UOM) def update_price_list_details(self): if self.price_list: price_list_details = frappe.db.get_value( From 7d607b82f1eecb4f778d5277c95267b0abd40415 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 29 Oct 2025 15:37:04 +0100 Subject: [PATCH 02/38] chore: resolve conflicts --- erpnext/stock/doctype/item_price/item_price.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 63cb604a28d..dc693890cd7 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -55,19 +55,11 @@ class ItemPrice(Document): if not frappe.db.exists("Item", self.item_code): frappe.throw(_("Item {0} not found.").format(self.item_code)) -<<<<<<< HEAD -======= if self.uom and not frappe.db.exists( "UOM Conversion Detail", {"parenttype": "Item", "parent": self.item_code, "uom": self.uom} ): frappe.throw(_("UOM {0} not found in Item {1}").format(self.uom, self.item_code)) - def validate_dates(self): - if self.valid_from and self.valid_upto: - if getdate(self.valid_from) > getdate(self.valid_upto): - frappe.throw(_("Valid From Date must be lesser than Valid Up To Date.")) - ->>>>>>> 69824eff80 (feat(Item Price): validate UOM) def update_price_list_details(self): if self.price_list: price_list_details = frappe.db.get_value( From a5ec0e4f5079a2a9919ee245124f1f53ec20bcaf Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Sun, 16 Nov 2025 06:07:55 +0000 Subject: [PATCH 03/38] fix(stock-entry): prevent default warehouse from overriding parent warehouse (cherry picked from commit 8b38578914d6bb2cd5de9912063a6cb5d6488dc4) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index a41a529ff2c..61c270bd170 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1338,8 +1338,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle this.frm.script_manager.copy_from_first_row("items", row, ["expense_account", "cost_center"]); } - if (!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse; - if (!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; + if (this.frm.doc.from_warehouse) row.s_warehouse = this.frm.doc.from_warehouse; + if (this.frm.doc.to_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; if (cint(frappe.user_defaults?.use_serial_batch_fields)) { frappe.model.set_value(row.doctype, row.name, "use_serial_batch_fields", 1); From 0a0177cb9e1af611c0d596eca97de38afa8d595c Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:04:17 +0530 Subject: [PATCH 04/38] fix: construct batch_nos and serial_nos to avoid NoneType error --- erpnext/stock/stock_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 47cb41852c2..f4956e1b600 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2240,9 +2240,11 @@ def validate_reserved_stock(kwargs): kwargs.ignore_voucher_nos = [kwargs.voucher_no] if kwargs.serial_no: + kwargs.serial_nos = kwargs.serial_no.split("\n") validate_reserved_serial_nos(kwargs) elif kwargs.batch_no: + kwargs.batch_nos = [kwargs.batch_no] validate_reserved_batch_nos(kwargs) elif kwargs.serial_and_batch_bundle: From 2a5c9b469c61995a6850dee32f5572541dc87223 Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:53:55 +0530 Subject: [PATCH 05/38] fix: enable allow_negative_stock settings --- .../doctype/stock_reconciliation/test_stock_reconciliation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index afbfcdd6062..61c32d09467 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -45,6 +45,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): def test_reco_for_moving_average(self): self._test_reco_sle_gle("Moving Average") + @change_settings("Stock Settings", {"allow_negative_stock": 1}) def _test_reco_sle_gle(self, valuation_method): item_code = self.make_item(properties={"valuation_method": valuation_method}).name From 623a0a932ec88d613256024b5fdade1a261df86d Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Mon, 17 Nov 2025 13:09:38 +0000 Subject: [PATCH 06/38] fix: add cancelled option in status field --- erpnext/assets/doctype/asset/asset.json | 5 ++--- erpnext/assets/doctype/asset/asset.py | 1 + erpnext/assets/doctype/asset_repair/asset_repair.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 487f67669ff..2e15dd77b62 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -371,7 +371,6 @@ "label": "Other Details" }, { - "allow_on_submit": 1, "default": "Draft", "fieldname": "status", "fieldtype": "Select", @@ -379,7 +378,7 @@ "in_standard_filter": 1, "label": "Status", "no_copy": 1, - "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nWork In Progress", + "options": "Draft\nSubmitted\nCancelled\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nWork In Progress", "read_only": 1 }, { @@ -597,7 +596,7 @@ "link_fieldname": "target_asset" } ], - "modified": "2025-10-23 22:43:33.634452", + "modified": "2025-11-17 18:01:51.417942", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 9f8895b6e7e..6ef5a8643aa 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -103,6 +103,7 @@ class Asset(AccountsController): status: DF.Literal[ "Draft", "Submitted", + "Cancelled", "Partially Depreciated", "Fully Depreciated", "Sold", diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json index 16f3da9b988..9b6d878378b 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.json +++ b/erpnext/assets/doctype/asset_repair/asset_repair.json @@ -139,7 +139,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Asset", - "link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",null]]]", + "link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",\"Cancelled\",null]]]", "options": "Asset", "reqd": 1 }, @@ -250,7 +250,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-07-29 15:14:34.044564", + "modified": "2025-11-17 18:35:54.575265", "modified_by": "Administrator", "module": "Assets", "name": "Asset Repair", From 57282999ad70b543aefb0e372b85675eb4b91bfb Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 12 Nov 2025 18:41:20 +0530 Subject: [PATCH 07/38] fix: back calcalute total amount from rate and tax_amount in tax withholding details report (cherry picked from commit d3751d9bb4eaddbdf4282f2f4adc45ee382a1ad0) --- .../tax_withholding_details.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 81dba55d609..c96510aa497 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -72,17 +72,28 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category") rate = get_tax_withholding_rates(tax_rate_map.get(tax_withholding_category, []), posting_date) - if net_total_map.get((voucher_type, name)): + + values = net_total_map.get((voucher_type, name)) + + if values: if voucher_type == "Journal Entry" and tax_amount and rate: # back calcalute total amount from rate and tax_amount - base_total = min(tax_amount / (rate / 100), net_total_map.get((voucher_type, name))[0]) + base_total = min(tax_amount / (rate / 100), values[0]) total_amount = grand_total = base_total - elif voucher_type == "Purchase Invoice": - total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get( - (voucher_type, name) - ) + else: - total_amount, grand_total, base_total = net_total_map.get((voucher_type, name)) + if tax_amount and rate: + # back calcalute total amount from rate and tax_amount + total_amount = (tax_amount * 100) / rate + else: + total_amount = values[0] + + grand_total = values[1] + base_total = values[2] + + if voucher_type == "Purchase Invoice": + bill_no = values[3] + bill_date = values[4] else: total_amount += entry.credit From c150e5795e9fbcf4132ac1c64f9d11c9c7a0a0c2 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 12 Nov 2025 18:59:45 +0530 Subject: [PATCH 08/38] fix: improve precision in tax amount calculations in tax withholding details report (cherry picked from commit 7c5f5405cc17ff1a56e55777af29507dbc512168) --- .../tax_withholding_details/tax_withholding_details.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index c96510aa497..169de9cd801 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -4,7 +4,9 @@ import frappe from frappe import _ -from frappe.utils import getdate +from frappe.utils import flt, getdate + +from erpnext.accounts.utils import get_currency_precision def execute(filters=None): @@ -43,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ party_map = get_party_pan_map(filters.get("party_type")) tax_rate_map = get_tax_rate_map(filters) gle_map = get_gle_map(tds_docs) + precision = get_currency_precision() out = [] entries = {} @@ -78,13 +81,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ if values: if voucher_type == "Journal Entry" and tax_amount and rate: # back calcalute total amount from rate and tax_amount - base_total = min(tax_amount / (rate / 100), values[0]) + base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0]) total_amount = grand_total = base_total else: if tax_amount and rate: # back calcalute total amount from rate and tax_amount - total_amount = (tax_amount * 100) / rate + total_amount = flt((tax_amount * 100) / rate, precision=precision) else: total_amount = values[0] From 4df80c5b53c678a2740eaca74fe14cd89ae7fbbb Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 12 Nov 2025 19:28:38 +0530 Subject: [PATCH 09/38] chore: typo in comment (cherry picked from commit e056c0327dc2043bb3baf43c6e73706f29bc31d4) --- .../report/tax_withholding_details/tax_withholding_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index 169de9cd801..78fc08614f2 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -80,13 +80,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_ if values: if voucher_type == "Journal Entry" and tax_amount and rate: - # back calcalute total amount from rate and tax_amount + # back calculate total amount from rate and tax_amount base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0]) total_amount = grand_total = base_total else: if tax_amount and rate: - # back calcalute total amount from rate and tax_amount + # back calculate total amount from rate and tax_amount total_amount = flt((tax_amount * 100) / rate, precision=precision) else: total_amount = values[0] From f0eac4703726e00f151231b9694f40a4b8048520 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Mon, 10 Nov 2025 17:34:19 +0530 Subject: [PATCH 10/38] fix: add doctype parameter to lead details for correct company details (cherry picked from commit 0b91338771ad8201c95b3766babd070a9162c01f) --- erpnext/controllers/selling_controller.py | 1 + erpnext/crm/doctype/lead/lead.py | 4 ++-- erpnext/selling/doctype/quotation/quotation.js | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index e4e2ee29d9b..ff8dec4db70 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -137,6 +137,7 @@ class SellingController(StockController): lead, posting_date=self.get("transaction_date") or self.get("posting_date"), company=self.company, + doctype=self.doctype, ) ) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index cf9a7f02f9d..f0f492191fb 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -432,7 +432,7 @@ def _set_missing_values(source, target): @frappe.whitelist() -def get_lead_details(lead, posting_date=None, company=None): +def get_lead_details(lead, posting_date=None, company=None, doctype=None): if not lead: return {} @@ -454,7 +454,7 @@ def get_lead_details(lead, posting_date=None, company=None): } ) - set_address_details(out, lead, "Lead", company=company) + set_address_details(out, lead, "Lead", doctype=doctype, company=company) taxes_and_charges = set_taxes( None, diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index da8476d6b1f..f0061c016bd 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -252,6 +252,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext. lead: this.frm.doc.party_name, posting_date: this.frm.doc.transaction_date, company: this.frm.doc.company, + doctype: this.frm.doc.doctype, }, callback: function (r) { if (r.message) { From 2db91ee67efe95f97528277636245d124eb275cc Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Mon, 17 Nov 2025 14:06:26 +0000 Subject: [PATCH 11/38] fix(asset repair): validate pi status --- erpnext/assets/doctype/asset_repair/asset_repair.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py index 358945edf87..77e191873a5 100644 --- a/erpnext/assets/doctype/asset_repair/asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/asset_repair.py @@ -60,6 +60,17 @@ class AssetRepair(AccountsController): if self.get("stock_items"): self.set_stock_items_cost() self.calculate_total_repair_cost() + self.validate_purchase_invoice_status() + + def validate_purchase_invoice_status(self): + if self.purchase_invoice: + docstatus = frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "docstatus") + if docstatus == 0: + frappe.throw( + _("{0} is still in Draft. Please submit it before saving the Asset Repair.").format( + get_link_to_form("Purchase Invoice", self.purchase_invoice) + ) + ) def validate_asset(self): if self.asset_doc.status in ("Sold", "Fully Depreciated", "Scrapped"): From ac40b596651fa1e206a7f2a481d7af7dbdc1add8 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Fri, 14 Nov 2025 16:46:10 +0530 Subject: [PATCH 12/38] fix(financial reports): set fiscal year associated with the default company --- erpnext/public/js/utils.js | 41 ++++++++++++++++++++++++-------------- erpnext/startup/boot.py | 8 ++++++++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 2b6d41c2d54..5942b34158d 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -420,25 +420,36 @@ $.extend(erpnext.utils, { if (!frappe.boot.setup_complete) { return; } + const today = frappe.datetime.get_today(); if (!date) { - date = frappe.datetime.get_today(); + date = today; } let fiscal_year = ""; - frappe.call({ - method: "erpnext.accounts.utils.get_fiscal_year", - args: { - date: date, - boolean: boolean, - }, - async: false, - callback: function (r) { - if (r.message) { - if (with_dates) fiscal_year = r.message; - else fiscal_year = r.message[0]; - } - }, - }); + if ( + frappe.boot.current_fiscal_year && + date >= frappe.boot.current_fiscal_year[1] && + date <= frappe.boot.current_fiscal_year[2] + ) { + if (with_dates) fiscal_year = frappe.boot.current_fiscal_year; + else fiscal_year = frappe.boot.current_fiscal_year[0]; + } else if (today != date) { + frappe.call({ + method: "erpnext.accounts.utils.get_fiscal_year", + type: "GET", // make it cacheable + args: { + date: date, + boolean: boolean, + }, + async: false, + callback: function (r) { + if (r.message) { + if (with_dates) fiscal_year = r.message; + else fiscal_year = r.message[0]; + } + }, + }); + } return fiscal_year; }, diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index 03cbd99f5c4..b17b6b49b6b 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -3,8 +3,11 @@ import frappe +from frappe.defaults import get_user_default from frappe.utils import cint +from erpnext.accounts.utils import get_fiscal_years + def boot_session(bootinfo): """boot session - send website info if guest""" @@ -53,6 +56,11 @@ def boot_session(bootinfo): ) party_account_types = frappe.db.sql(""" select name, ifnull(account_type, '') from `tabParty Type`""") + fiscal_year = get_fiscal_years( + frappe.utils.nowdate(), company=get_user_default("company"), boolean=True + ) + if fiscal_year: + bootinfo.current_fiscal_year = fiscal_year[0] bootinfo.party_account_types = frappe._dict(party_account_types) bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company") From 81a16286a144bd45d2f1c1fcb78d57c4c9c07869 Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin-114@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:37:26 +0530 Subject: [PATCH 13/38] fix: unintended backported depends_on expression (#50529) Co-authored-by: Kavin <78342682+kavin0411@users.noreply.github.com> --- erpnext/manufacturing/doctype/bom/bom.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 175c5818c43..04774d4f2a8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -591,7 +591,6 @@ }, { "default": "0", - "depends_on": "eval:doc.track_semi_finished_goods === 0", "fieldname": "fg_based_operating_cost", "fieldtype": "Check", "label": "Finished Goods based Operating Cost" From 799119ad3ee843e911cd1aa0366b252855984f2a Mon Sep 17 00:00:00 2001 From: Logesh Periyasamy Date: Tue, 18 Nov 2025 16:45:15 +0530 Subject: [PATCH 14/38] fix(general_ledger): add translation for accounting dimension (cherry picked from commit 113ff17c718770185db29c229ad3598f77624399) --- erpnext/accounts/report/general_ledger/general_ledger.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 7c6c809b939..100dcd46c85 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -566,6 +566,13 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot else: update_value_in_dict(consolidated_gle, key, gle) + if filters.get("include_dimensions"): + dimensions = [*accounting_dimensions, "cost_center", "project"] + + for dimension in dimensions: + if val := gle.get(dimension): + gle[dimension] = _(val) + for value in consolidated_gle.values(): update_value_in_dict(totals, "total", value) update_value_in_dict(totals, "closing", value) From a2c82b4dc3c09da9923e92c7d62e462b262b24d7 Mon Sep 17 00:00:00 2001 From: Navin-S-R Date: Fri, 14 Nov 2025 14:05:47 +0530 Subject: [PATCH 15/38] fix: use dynamic account type to get average ratio balance (cherry picked from commit 9118f08e7b571edb7bc81780806018fdfc85182e) --- erpnext/accounts/report/financial_ratios/financial_ratios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.py b/erpnext/accounts/report/financial_ratios/financial_ratios.py index 5084b6c1651..a726043d1d8 100644 --- a/erpnext/accounts/report/financial_ratios/financial_ratios.py +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.py @@ -199,7 +199,7 @@ def add_turnover_ratios(data, years, period_list, filters, total_asset, net_sale avg_data = {} for d in ["Receivable", "Payable", "Stock"]: - avg_data[frappe.scrub(d)] = avg_ratio_balance("Receivable", period_list, precision, filters) + avg_data[frappe.scrub(d)] = avg_ratio_balance(d, period_list, precision, filters) avg_debtors, avg_creditors, avg_stock = ( avg_data.get("receivable"), From 627b34a120f8086d33967b5062bfa1b9dd85dbe5 Mon Sep 17 00:00:00 2001 From: Navin-S-R Date: Fri, 14 Nov 2025 14:12:29 +0530 Subject: [PATCH 16/38] fix: correct profit after tax calculation by reducing expenses from income (cherry picked from commit f420371a7e77f383878afd1fffae15646dab60b0) --- erpnext/accounts/report/financial_ratios/financial_ratios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/financial_ratios/financial_ratios.py b/erpnext/accounts/report/financial_ratios/financial_ratios.py index a726043d1d8..48047c81944 100644 --- a/erpnext/accounts/report/financial_ratios/financial_ratios.py +++ b/erpnext/accounts/report/financial_ratios/financial_ratios.py @@ -174,7 +174,7 @@ def add_solvency_ratios( return_on_equity_ratio = {"ratio": _("Return on Equity Ratio")} for year in years: - profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year)) + profit_after_tax = flt(total_income.get(year)) - flt(total_expense.get(year)) share_holder_fund = flt(total_asset.get(year)) - flt(total_liability.get(year)) debt_equity_ratio[year] = calculate_ratio(total_liability.get(year), share_holder_fund, precision) From 2d6640ac61fc8fdf51649b54f9b55dbce879b2eb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:05:47 +0530 Subject: [PATCH 17/38] fix: add condition for allow negative stock in pos (backport #50369) (#50600) Co-authored-by: Logesh Periyasamy fix: add condition for allow negative stock in pos (#50369) --- .../doctype/pos_invoice/pos_invoice.py | 21 ++++++++++++------- .../page/point_of_sale/point_of_sale.py | 4 ++-- .../page/point_of_sale/pos_controller.js | 4 ++++ 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0196b2b6189..80d75bfddc3 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -189,6 +189,9 @@ class POSInvoice(SalesInvoice): super().__init__(*args, **kwargs) def validate(self): + if not self.customer: + frappe.throw(_("Please select Customer first")) + if not cint(self.is_pos): frappe.throw( _("POS Invoice should have the field {0} checked.").format(frappe.bold(_("Include Payment"))) @@ -345,14 +348,14 @@ class POSInvoice(SalesInvoice): ): return - from erpnext.stock.stock_ledger import is_negative_stock_allowed - for d in self.get("items"): if not d.serial_and_batch_bundle: - if is_negative_stock_allowed(item_code=d.item_code): - return + available_stock, is_stock_item, is_negative_stock_allowed = get_stock_availability( + d.item_code, d.warehouse + ) - available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse) + if is_negative_stock_allowed: + continue item_code, warehouse, _qty = ( frappe.bold(d.item_code), @@ -760,20 +763,22 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): + from erpnext.stock.stock_ledger import is_negative_stock_allowed + if frappe.db.get_value("Item", item_code, "is_stock_item"): is_stock_item = True bin_qty = get_bin_qty(item_code, warehouse) pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) - return bin_qty - pos_sales_qty, is_stock_item + return bin_qty - pos_sales_qty, is_stock_item, is_negative_stock_allowed(item_code=item_code) else: is_stock_item = True if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}): - return get_bundle_availability(item_code, warehouse), is_stock_item + return get_bundle_availability(item_code, warehouse), is_stock_item, False else: is_stock_item = False # Is a service item or non_stock item - return 0, is_stock_item + return 0, is_stock_item, False def get_bundle_availability(bundle_item_code, warehouse): diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index d8de762dcd3..df488dafeb0 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -55,7 +55,7 @@ def search_by_term(search_term, warehouse, price_list): } ) - item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) + item_stock_qty, is_stock_item, is_negative_stock_allowed = get_stock_availability(item_code, warehouse) item_stock_qty = item_stock_qty // item.get("conversion_factor", 1) item.update({"actual_qty": item_stock_qty}) @@ -198,7 +198,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te current_date = frappe.utils.today() for item in items_data: - item.actual_qty, _ = get_stock_availability(item.item_code, warehouse) + item.actual_qty, _, is_negative_stock_allowed = get_stock_availability(item.item_code, warehouse) item_prices = frappe.get_all( "Item Price", diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 5e3218a67a2..6506ba047d0 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -759,12 +759,16 @@ erpnext.PointOfSale.Controller = class { const resp = (await this.get_available_stock(item_row.item_code, warehouse)).message; const available_qty = resp[0]; const is_stock_item = resp[1]; + const is_negative_stock_allowed = resp[2]; frappe.dom.unfreeze(); const bold_uom = item_row.stock_uom.bold(); const bold_item_code = item_row.item_code.bold(); const bold_warehouse = warehouse.bold(); const bold_available_qty = available_qty.toString().bold(); + + if (is_negative_stock_allowed) return; + if (!(available_qty > 0)) { if (is_stock_item) { frappe.model.clear_doc(item_row.doctype, item_row.name); From f8294f17543283faf248ba878732d607a60f3f34 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:05:58 +0530 Subject: [PATCH 18/38] feat(Company): allow setting default sales contact, fetch into sales transaction (backport #50159) (#50599) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: Diptanil Saha --- erpnext/controllers/selling_controller.py | 8 ++++++++ erpnext/public/js/utils/sales_common.js | 22 ++++++++++++++++++++++ erpnext/setup/doctype/company/company.js | 7 +++++++ erpnext/setup/doctype/company/company.json | 9 ++++++++- erpnext/setup/doctype/company/company.py | 1 + 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index ff8dec4db70..d74ea55450a 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -95,6 +95,7 @@ class SellingController(StockController): # set contact and address details for customer, if they are not mentioned self.set_missing_lead_customer_details(for_validate=for_validate) self.set_price_list_and_item_details(for_validate=for_validate) + self.set_company_contact_person() def set_missing_lead_customer_details(self, for_validate=False): customer, lead = None, None @@ -150,6 +151,13 @@ class SellingController(StockController): self.set_price_list_currency("Selling") self.set_missing_item_details(for_validate=for_validate) + def set_company_contact_person(self): + """Set the Company's Default Sales Contact as Company Contact Person.""" + if self.company and self.meta.has_field("company_contact_person") and not self.company_contact_person: + self.company_contact_person = frappe.get_cached_value( + "Company", self.company, "default_sales_contact" + ) + def remove_shipping_charge(self): if self.shipping_rule: shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule) diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index bf4ef8666cd..7e2271dc38f 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -115,6 +115,10 @@ erpnext.sales_common = { company() { super.company(); this.set_default_company_address(); + if (!this.is_onload) { + // we don't want to override the mapped contact from prevdoc + this.set_default_company_contact_person(); + } } set_default_company_address() { @@ -139,6 +143,24 @@ erpnext.sales_common = { } } + set_default_company_contact_person() { + if (!frappe.meta.has_field(this.frm.doc.doctype, "company_contact_person")) { + return; + } + + if (this.frm.doc.company) { + frappe.db + .get_value("Company", this.frm.doc.company, "default_sales_contact") + .then((r) => { + if (r.message?.default_sales_contact) { + this.frm.set_value("company_contact_person", r.message.default_sales_contact); + } else { + this.frm.set_value("company_contact_person", ""); + } + }); + } + } + customer() { var me = this; erpnext.utils.get_party_details(this.frm, null, null, function () { diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index f736769b915..032ec707330 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -37,6 +37,13 @@ frappe.ui.form.on("Company", { return { filters: { selling: 1 } }; }); + frm.set_query("default_sales_contact", function (doc) { + return { + query: "frappe.contacts.doctype.contact.contact.contact_query", + filters: { link_doctype: "Company", link_name: doc.name }, + }; + }); + frm.set_query("default_buying_terms", function () { return { filters: { buying: 1 } }; }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 8c4f85ff19f..fc6533a1e89 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -103,6 +103,7 @@ "total_monthly_sales", "column_break_goals", "default_selling_terms", + "default_sales_contact", "default_warehouse_for_sales_return", "credit_limit", "transactions_annual_history", @@ -851,6 +852,12 @@ "fieldtype": "Select", "label": "Reconciliation Takes Effect On", "options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date" + }, + { + "fieldname": "default_sales_contact", + "fieldtype": "Link", + "label": "Default Sales Contact", + "options": "Contact" } ], "icon": "fa fa-building", @@ -858,7 +865,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2025-08-25 18:34:03.602046", + "modified": "2025-11-16 16:51:27.624096", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index f9978099ed5..50fe87ef654 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -66,6 +66,7 @@ class Company(NestedSet): default_payable_account: DF.Link | None default_provisional_account: DF.Link | None default_receivable_account: DF.Link | None + default_sales_contact: DF.Link | None default_selling_terms: DF.Link | None default_warehouse_for_sales_return: DF.Link | None depreciation_cost_center: DF.Link | None From 85c0c1696430d75af4f6d2c9cd527e96a699335c Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:32:41 +0530 Subject: [PATCH 19/38] fix: validate sabb autocreation when disabled (cherry picked from commit 3ca19408811bbf26819c6ba65fc9522a8c80aa4a) # Conflicts: # erpnext/stock/doctype/stock_entry/stock_entry.py --- erpnext/stock/doctype/stock_entry/stock_entry.py | 9 +++++++++ erpnext/stock/serial_batch_bundle.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 76c57ae873d..8d72aae7506 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1135,8 +1135,17 @@ class StockEntry(StockController): "ignore_serial_nos": already_picked_serial_nos, "qty": row.transfer_qty * -1, } +<<<<<<< HEAD ).update_serial_and_batch_entries() elif not row.serial_and_batch_bundle: +======= + ).update_serial_and_batch_entries( + serial_nos=serial_nos.get(row.name), batch_nos=batch_nos.get(row.name) + ) + elif not row.serial_and_batch_bundle and frappe.get_single_value( + "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward" + ): +>>>>>>> 3ca1940881 (fix: validate sabb autocreation when disabled) bundle_doc = SerialBatchCreation( { "item_code": row.item_code, diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index db9cf422b5f..2ee85fc2c24 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -259,7 +259,7 @@ class SerialBatchBundle: and not self.sle.serial_and_batch_bundle and self.item_details.has_batch_no == 1 and ( - self.item_details.create_new_batch + (self.item_details.create_new_batch and self.sle.actual_qty > 0) or ( frappe.db.get_single_value( "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward" From 8ccb9a5ad2049bbc5effb98095e6cbbd41b14a75 Mon Sep 17 00:00:00 2001 From: Pugazhendhi Velu Date: Thu, 13 Nov 2025 08:54:44 +0000 Subject: [PATCH 20/38] fix: add return status for purchase receipt (cherry picked from commit 3a0e1e8ef95603c98a9c4ca9e66694ac32cd257d) --- erpnext/controllers/status_updater.py | 1 + .../doctype/purchase_receipt/purchase_receipt.json | 4 ++-- .../stock/doctype/purchase_receipt/purchase_receipt.py | 10 +++++++++- .../doctype/purchase_receipt/purchase_receipt_list.js | 2 +- .../doctype/purchase_receipt/test_purchase_receipt.py | 3 ++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 15c2760332e..0e94cfd95de 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -93,6 +93,7 @@ status_map = { ["Draft", None], ["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"], ["Partly Billed", "eval:self.per_billed > 0 and self.per_billed < 100 and self.docstatus == 1"], + ["Return", "eval:self.is_return == 1 and self.per_billed == 0 and self.docstatus == 1"], ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], [ "Completed", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 603f4a121d1..f220ed3736e 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -893,7 +893,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nPartly Billed\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed", + "options": "\nDraft\nPartly Billed\nTo Bill\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed", "print_hide": 1, "print_width": "150px", "read_only": 1, @@ -1300,7 +1300,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2025-08-06 16:41:02.690658", + "modified": "2025-11-12 19:53:48.173096", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c4f878fe85d..53aad87e2da 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -117,7 +117,15 @@ class PurchaseReceipt(BuyingController): shipping_address_display: DF.SmallText | None shipping_rule: DF.Link | None status: DF.Literal[ - "", "Draft", "Partly Billed", "To Bill", "Completed", "Return Issued", "Cancelled", "Closed" + "", + "Draft", + "Partly Billed", + "To Bill", + "Completed", + "Return", + "Return Issued", + "Cancelled", + "Closed", ] subcontracting_receipt: DF.Link | None supplied_items: DF.Table[PurchaseReceiptItemSupplied] diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js index d70b357d731..30562e23de8 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js @@ -11,7 +11,7 @@ frappe.listview_settings["Purchase Receipt"] = { "currency", ], get_indicator: function (doc) { - if (cint(doc.is_return) == 1) { + if (cint(doc.is_return) == 1 && doc.status == "Return") { return [__("Return"), "gray", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 383421f3c67..959c58b2380 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -455,6 +455,7 @@ class TestPurchaseReceipt(FrappeTestCase): # Check if Original PR updated self.assertEqual(pr.items[0].returned_qty, 2) self.assertEqual(pr.per_returned, 40) + self.assertEqual(returned.status, "Return") from erpnext.controllers.sales_and_purchase_return import make_return_doc @@ -2128,7 +2129,7 @@ class TestPurchaseReceipt(FrappeTestCase): return_pr.items[0].stock_qty = 0.0 return_pr.submit() - self.assertEqual(return_pr.status, "To Bill") + self.assertEqual(return_pr.status, "Return") pi = make_purchase_invoice(return_pr.name) pi.submit() From 36e9aae9d05770598ecb839b259a94e1fcad04d0 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 18 Nov 2025 18:04:29 +0530 Subject: [PATCH 21/38] chore: fix conflicts --- erpnext/stock/doctype/stock_entry/stock_entry.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8d72aae7506..0ead7461d67 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1135,17 +1135,9 @@ class StockEntry(StockController): "ignore_serial_nos": already_picked_serial_nos, "qty": row.transfer_qty * -1, } -<<<<<<< HEAD - ).update_serial_and_batch_entries() - elif not row.serial_and_batch_bundle: -======= - ).update_serial_and_batch_entries( - serial_nos=serial_nos.get(row.name), batch_nos=batch_nos.get(row.name) - ) elif not row.serial_and_batch_bundle and frappe.get_single_value( "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward" ): ->>>>>>> 3ca1940881 (fix: validate sabb autocreation when disabled) bundle_doc = SerialBatchCreation( { "item_code": row.item_code, From 876dec5077217788df16c0cc109b110e6bc8b809 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 18 Nov 2025 18:05:08 +0530 Subject: [PATCH 22/38] chore: fix conflicts --- erpnext/stock/doctype/stock_entry/stock_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0ead7461d67..9664ed67498 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1135,6 +1135,7 @@ class StockEntry(StockController): "ignore_serial_nos": already_picked_serial_nos, "qty": row.transfer_qty * -1, } + ).update_serial_and_batch_entries() elif not row.serial_and_batch_bundle and frappe.get_single_value( "Stock Settings", "auto_create_serial_and_batch_bundle_for_outward" ): From 5b1674018b9e58d60fe657b0494c4901dec7e01c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Nov 2025 12:11:56 +0530 Subject: [PATCH 23/38] fix: redundant message on bom save (cherry picked from commit 074f07694fb04553c034ca84d0f7ec7797bbec7c) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py --- erpnext/manufacturing/doctype/bom/bom.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 22264fb0e92..af1eac402e6 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -465,7 +465,7 @@ class BOM(WebsiteGenerator): ) ) - def get_rm_rate(self, arg): + def get_rm_rate(self, arg, notify=True): """Get raw material rate as per selected method, if bom exists takes bom cost""" rate = 0 if not self.rm_cost_as_per: @@ -491,7 +491,7 @@ class BOM(WebsiteGenerator): ), alert=True, ) - else: + elif notify: frappe.msgprint( _("{0} not found for item {1}").format(self.rm_cost_as_per, arg["item_code"]), alert=True, @@ -796,7 +796,13 @@ class BOM(WebsiteGenerator): "stock_uom": d.stock_uom, "conversion_factor": d.conversion_factor, "sourced_by_supplier": d.sourced_by_supplier, +<<<<<<< HEAD } +======= + "is_phantom_item": d.is_phantom_item, + }, + notify=False, +>>>>>>> 074f07694f (fix: redundant message on bom save) ) d.base_rate = flt(d.rate) * flt(self.conversion_rate) From 2550b44db88869b7cc23eb32d19e36d8bbf9cb30 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Nov 2025 12:38:47 +0530 Subject: [PATCH 24/38] chore: resolve conflicts --- erpnext/manufacturing/doctype/bom/bom.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index af1eac402e6..89f90e1658c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -796,13 +796,8 @@ class BOM(WebsiteGenerator): "stock_uom": d.stock_uom, "conversion_factor": d.conversion_factor, "sourced_by_supplier": d.sourced_by_supplier, -<<<<<<< HEAD - } -======= - "is_phantom_item": d.is_phantom_item, }, notify=False, ->>>>>>> 074f07694f (fix: redundant message on bom save) ) d.base_rate = flt(d.rate) * flt(self.conversion_rate) From 015f946a1471e1c062ae27d1d30c7f23e6053950 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Nov 2025 16:07:50 +0530 Subject: [PATCH 25/38] fix: add filter company and status to job card employee (cherry picked from commit 3ca3a6d9bba4878a73172cd5cf49976742007b0f) --- erpnext/manufacturing/doctype/job_card/job_card.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 9d3c646598b..69d0cc8fbd8 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -38,6 +38,15 @@ frappe.ui.form.on("Job Card", { return doc.status === "Complete" ? "green" : "orange"; } }); + + frm.set_query("employee", () => { + return { + filters: { + company: frm.doc.company, + status: "Active", + }, + }; + }); }, refresh: function (frm) { From 1d6e3e4e7dd39d9fad07eca6b205fbe88b358e78 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 19 Nov 2025 16:13:56 +0530 Subject: [PATCH 26/38] fix: show current company warehouse only in get material from bom MR (cherry picked from commit 3271eaaf0ebbb16553724a11620e9fbffc4cf9f2) --- erpnext/stock/doctype/material_request/material_request.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 243ba7adac8..f295f33853f 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -332,6 +332,9 @@ frappe.ui.form.on("Material Request", { label: __("For Warehouse"), options: "Warehouse", reqd: 1, + get_query: function () { + return { filters: { company: frm.doc.company } }; + }, }, { fieldname: "qty", fieldtype: "Float", label: __("Quantity"), reqd: 1, default: 1 }, { From c85ce55f275c4627b29e40508ab7805ff379a5c7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 11:30:39 +0000 Subject: [PATCH 27/38] Merge pull request #50631 from frappe/mergify/bp/version-15-hotfix/pr-50629 fix: process loss % can be negative (backport #50629) --- erpnext/manufacturing/doctype/bom/bom.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 04774d4f2a8..ab8586aaef1 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -548,12 +548,14 @@ { "fieldname": "process_loss_percentage", "fieldtype": "Percent", - "label": "% Process Loss" + "label": "% Process Loss", + "non_negative": 1 }, { "fieldname": "process_loss_qty", "fieldtype": "Float", "label": "Process Loss Qty", + "non_negative": 1, "read_only": 1 }, { @@ -639,7 +641,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2025-10-29 17:43:12.966753", + "modified": "2025-11-19 16:17:15.925156", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From 673b893942ca789d7ea554120b75c2f444b28780 Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Wed, 19 Nov 2025 01:23:26 +0530 Subject: [PATCH 28/38] fix: ignore reserved batches from total available batches --- .../serial_and_batch_bundle/serial_and_batch_bundle.py | 6 +++++- erpnext/stock/stock_ledger.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) 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 d9071c406a2..690e3c81c92 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 @@ -2297,7 +2297,11 @@ def get_auto_batch_nos(kwargs): stock_ledgers_batches = get_stock_ledgers_batches(kwargs) pos_invoice_batches = get_reserved_batches_for_pos(kwargs) - sre_reserved_batches = get_reserved_batches_for_sre(kwargs) + + sre_reserved_batches = frappe._dict() + if not kwargs.ignore_reserved_stock: + sre_reserved_batches = get_reserved_batches_for_sre(kwargs) + picked_batches = frappe._dict() if kwargs.get("is_pick_list"): picked_batches = get_picked_batches(kwargs) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f4956e1b600..8b61ca56983 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2313,6 +2313,7 @@ def validate_reserved_batch_nos(kwargs): "posting_date": kwargs.posting_date, "posting_time": kwargs.posting_time, "ignore_voucher_nos": kwargs.ignore_voucher_nos, + "ignore_reserved_stock": True, } ) ) From 55f2f1c515133a1fdf2152bd44e88f26c965267f Mon Sep 17 00:00:00 2001 From: Kavin <78342682+kavin0411@users.noreply.github.com> Date: Wed, 19 Nov 2025 01:24:07 +0530 Subject: [PATCH 29/38] test: add unit test for reserved stock validation --- .../test_stock_reservation_entry.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py index ddbf0b2dc25..942c7f482ae 100644 --- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py +++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py @@ -678,6 +678,36 @@ class TestStockReservationEntry(FrappeTestCase): # Test - 1: ValidationError should be thrown as the inwarded stock is reserved. self.assertRaises(frappe.ValidationError, se.cancel) + @change_settings("Stock Settings", {"allow_negative_stock": 0, "enable_stock_reservation": 1}) + def test_reserved_stock_validation_for_batch_item(self): + item_properties = { + "is_stock_item": 1, + "valuation_rate": 100, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SRBV-.#####.", + } + sr_item = make_item(item_code="Test Reserve Item", properties=item_properties) + # inward 100 qty of stock + create_material_receipt(items={sr_item.name: sr_item}, warehouse=self.warehouse, qty=100) + + # reserve 80 qty from sales order + so = make_sales_order(item_code=sr_item.name, warehouse=self.warehouse, qty=80) + so.create_stock_reservation_entries() + + # create a material issue entry including the reserved qty 10 + se = make_stock_entry( + item_code=sr_item.name, + qty=30, + from_warehouse=self.warehouse, + rate=100, + purpose="Material Issue", + do_not_submit=True, + ) + + # validation for reserved stock should be thrown + self.assertRaises(frappe.ValidationError, se.submit) + def tearDown(self) -> None: cancel_all_stock_reservation_entries() return super().tearDown() From 89fcdbf56b5496d53ab432e1283df5992df6ec88 Mon Sep 17 00:00:00 2001 From: Karuppasamy B Date: Thu, 13 Nov 2025 01:16:48 +0530 Subject: [PATCH 30/38] fix(purchase_receipt): add internal_and_external_links field to show purchase invoice connection count (cherry picked from commit 6c1620ab8cb816733de4e613af67ff4374ed0f84) --- .../doctype/purchase_receipt/purchase_receipt_dashboard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index b1b0a962246..628b4628f79 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -18,6 +18,9 @@ def get_data(): "Purchase Order": ["items", "purchase_order"], "Project": ["items", "project"], }, + "internal_and_external_links": { + "Purchase Invoice": ["items", "purchase_invoice"], + }, "transactions": [ { "label": _("Related"), From 6b6e017e36e4367afe41204c2c3c4754719325f5 Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Fri, 31 Oct 2025 14:00:37 +0530 Subject: [PATCH 31/38] feat: modify accounting dimension as multiselect field (cherry picked from commit 3fcd8d84acfa8aac0e4e01b860e153a1c3aa1f29) --- .../accounts_payable/accounts_payable.js | 15 +++++------- .../accounts_payable_summary.js | 15 +++++------- .../accounts_receivable.js | 15 +++++------- .../accounts_receivable_summary.js | 15 +++++------- .../report/trial_balance/trial_balance.js | 23 ++++++++++--------- 5 files changed, 36 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 7c3ced847ad..9af8c93a52e 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -26,16 +26,13 @@ frappe.query_reports["Accounts Payable"] = { { fieldname: "cost_center", label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center", - get_query: () => { - var company = frappe.query_report.get_filter_value("company"); - return { - filters: { - company: company, - }, - }; + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, + options: "Cost Center", }, { fieldname: "party_account", diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index ac9d5bfbd01..18a85af95be 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -45,16 +45,13 @@ frappe.query_reports["Accounts Payable Summary"] = { { fieldname: "cost_center", label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center", - get_query: () => { - var company = frappe.query_report.get_filter_value("company"); - return { - filters: { - company: company, - }, - }; + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, + options: "Cost Center", }, { fieldname: "party_type", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index c5bfe4de0ff..b052d50838d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -28,16 +28,13 @@ frappe.query_reports["Accounts Receivable"] = { { fieldname: "cost_center", label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center", - get_query: () => { - var company = frappe.query_report.get_filter_value("company"); - return { - filters: { - company: company, - }, - }; + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, + options: "Cost Center", }, { fieldname: "party_type", diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index ae0bddaa766..1ac2b27ca71 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -45,16 +45,13 @@ frappe.query_reports["Accounts Receivable Summary"] = { { fieldname: "cost_center", label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center", - get_query: () => { - var company = frappe.query_report.get_filter_value("company"); - return { - filters: { - company: company, - }, - }; + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, + options: "Cost Center", }, { fieldname: "party_type", diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index 2f701900cf7..8c08c35b3e1 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -47,22 +47,23 @@ frappe.query_reports["Trial Balance"] = { { fieldname: "cost_center", label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center", - get_query: function () { - var company = frappe.query_report.get_filter_value("company"); - return { - doctype: "Cost Center", - filters: { - company: company, - }, - }; + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, + options: "Cost Center", }, { fieldname: "project", label: __("Project"), - fieldtype: "Link", + fieldtype: "MultiSelectList", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, options: "Project", }, { From 02a1f815da9f8df1ab21142087fe15a7f741446e Mon Sep 17 00:00:00 2001 From: l0gesh29 Date: Fri, 31 Oct 2025 19:16:35 +0530 Subject: [PATCH 32/38] feat(reports): preserve accounting dimension filters while navigating between reports (cherry picked from commit fcfcaa76c6a2cf190655c226ba773053aa88bb42) --- .../accounts_receivable.py | 7 ++----- .../report/trial_balance/trial_balance.py | 15 +++---------- erpnext/public/js/financial_statements.js | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 1fb93846735..9ca930bd829 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -15,6 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, get_dimension_with_children, ) +from erpnext.accounts.report.financial_statements import get_cost_centers_with_children from erpnext.accounts.utils import ( build_qb_match_conditions, get_advance_payment_doctypes, @@ -994,11 +995,7 @@ class ReceivablePayableReport: self.add_accounting_dimensions_filters() def get_cost_center_conditions(self): - lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"]) - cost_center_list = [ - center.name - for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)}) - ] + cost_center_list = get_cost_centers_with_children(self.filters.cost_center) self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list)) def add_common_filters(self): diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 3f75f1e2b4c..2ea76c82975 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -15,6 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.report.financial_statements import ( filter_accounts, filter_out_zero_value_rows, + get_cost_centers_with_children, set_gl_entries_by_account, ) from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency @@ -103,10 +104,6 @@ def get_data(filters): opening_balances = get_opening_balances(filters, ignore_is_opening) - # add filter inside list so that the query in financial_statements.py doesn't break - if filters.project: - filters.project = [filters.project] - set_gl_entries_by_account( filters.company, filters.from_date, @@ -270,18 +267,12 @@ def get_opening_balance( opening_balance = opening_balance.where(closing_balance.voucher_type != "Period Closing Voucher") if filters.cost_center: - lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"]) - cost_center = frappe.qb.DocType("Cost Center") opening_balance = opening_balance.where( - closing_balance.cost_center.isin( - frappe.qb.from_(cost_center) - .select("name") - .where((cost_center.lft >= lft) & (cost_center.rgt <= rgt)) - ) + closing_balance.cost_center.isin(get_cost_centers_with_children(filters.get("cost_center"))) ) if filters.project: - opening_balance = opening_balance.where(closing_balance.project == filters.project) + opening_balance = opening_balance.where(closing_balance.project.isin(filters.project)) if frappe.db.count("Finance Book"): if filters.get("include_default_book_entries"): diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index f9eddcf2889..e6f257d39b8 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -79,18 +79,35 @@ erpnext.financial_statements = { }, open_general_ledger: function (data) { if (!data.account && !data.accounts) return; - let project = $.grep(frappe.query_report.filters, function (e) { + let filters = frappe.query_report.filters; + + let project = $.grep(filters, function (e) { return e.df.fieldname == "project"; }); + let cost_center = $.grep(filters, function (e) { + return e.df.fieldname == "cost_center"; + }); + frappe.route_options = { account: data.account || data.accounts, company: frappe.query_report.get_filter_value("company"), from_date: data.from_date || data.year_start_date, to_date: data.to_date || data.year_end_date, - project: project && project.length > 0 ? project[0].$input.val() : "", + project: project && project.length > 0 ? project[0].get_value() : "", + cost_center: cost_center && cost_center.length > 0 ? cost_center[0].get_value() : "", }; + filters.forEach((f) => { + if (f.df.fieldtype == "MultiSelectList") { + if (f.df.fieldname in frappe.route_options) return; + let value = f.get_value(); + if (value && value.length > 0) { + frappe.route_options[f.df.fieldname] = value; + } + } + }); + let report = "General Ledger"; if (["Payable", "Receivable"].includes(data.account_type)) { From 0bc98b609fcf70a8037038671a8ebd357caffada Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 20 Nov 2025 13:00:24 +0530 Subject: [PATCH 33/38] fix: validation for SABB deletion (cherry picked from commit dd4bef070651333f9ce33edb2b0b334a45a34b5e) --- .../serial_and_batch_bundle.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) 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 690e3c81c92..3e8df4dd8e5 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 @@ -1347,7 +1347,36 @@ class SerialandBatchBundle(Document): if self.voucher_type == "POS Invoice": return - if frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1: + child_doctype = self.voucher_type + " Item" + mapper = { + "Asset Capitalization": "Asset Capitalization Stock Item", + "Asset Repair": "Asset Repair Consumed Item", + "Stock Entry": "Stock Entry Detail", + }.get(self.voucher_type) + + if mapper: + child_doctype = mapper + + if self.voucher_type == "Delivery Note" and not frappe.db.exists( + "Delivery Note Item", self.voucher_detail_no + ): + child_doctype = "Packed Item" + + elif self.voucher_type == "Sales Invoice" and not frappe.db.exists( + "Sales Invoice Item", self.voucher_detail_no + ): + child_doctype = "Packed Item" + + elif self.voucher_type == "Subcontracting Receipt" and not frappe.db.exists( + "Subcontracting Receipt Item", self.voucher_detail_no + ): + child_doctype = "Subcontracting Receipt Supplied Item" + + if ( + frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1 + and self.voucher_detail_no + and frappe.db.exists(child_doctype, self.voucher_detail_no) + ): msg = f"""The {self.voucher_type} {bold(self.voucher_no)} is in submitted state, please cancel it first""" frappe.throw(_(msg)) From 25cd230471060d2672ea754cc3ec933d8eceeb1c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Nov 2025 14:57:33 +0530 Subject: [PATCH 34/38] fix: serial batch selector shown only once (cherry picked from commit aa6f09e9a9f9466680235a1246b7852466b6369b) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 61c270bd170..6a6bb4c95b4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -951,12 +951,10 @@ frappe.ui.form.on("Stock Entry Detail", { no_batch_serial_number_value = true; } - if ( - no_batch_serial_number_value && - !frappe.flags.hide_serial_batch_dialog && - !frappe.flags.dialog_set - ) { - frappe.flags.dialog_set = true; + if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) { + if (!frappe.flags.dialog_set) { + frappe.flags.dialog_set = true; + } erpnext.stock.select_batch_and_serial_no(frm, d); } else { frappe.flags.dialog_set = false; From aa94c91c12689e04d56c24e7a55d567f65609951 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Nov 2025 15:42:27 +0530 Subject: [PATCH 35/38] fix: remove disabled warehouse in get_warehouses_based_on_account (cherry picked from commit ff2d9bf4cb49d384083a2c0dadf1d7571246703b) --- erpnext/stock/doctype/warehouse/warehouse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 4600c2bbaae..a1991dd9c07 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -221,7 +221,9 @@ def get_child_warehouses(warehouse): def get_warehouses_based_on_account(account, company=None): warehouses = [] - for d in frappe.get_all("Warehouse", fields=["name", "is_group"], filters={"account": account}): + for d in frappe.get_all( + "Warehouse", fields=["name", "is_group"], filters={"account": account, "disabled": 0} + ): if d.is_group: warehouses.extend(get_child_warehouses(d.name)) else: From a24733791d3070a89d06c29a8e39751623e5fe4b Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Nov 2025 15:34:39 +0530 Subject: [PATCH 36/38] fix: unhide zero val checkbox (cherry picked from commit 20e0313a8c4d1d94a19cc9e82d9f04ad31a059f2) # Conflicts: # erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json --- .../doctype/stock_reconciliation/stock_reconciliation.py | 1 + .../stock_reconciliation_item.json | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 937b130cf7b..cc4b08b50f4 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -979,6 +979,7 @@ class StockReconciliation(StockController): is_customer_item = frappe.db.get_value("Item", d.item_code, "is_customer_provided_item") if is_customer_item and d.valuation_rate: d.valuation_rate = 0.0 + d.allow_zero_valuation_rate = 1 changed_any_values = True if changed_any_values: diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 4d3a87c70aa..47e59d7e9e8 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -185,12 +185,10 @@ }, { "default": "0", - "depends_on": "allow_zero_valuation_rate", "fieldname": "allow_zero_valuation_rate", "fieldtype": "Check", "label": "Allow Zero Valuation Rate", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "depends_on": "barcode", @@ -256,7 +254,11 @@ ], "istable": 1, "links": [], +<<<<<<< HEAD "modified": "2025-03-12 16:34:51.326821", +======= + "modified": "2025-11-20 15:27:13.868179", +>>>>>>> 20e0313a8c (fix: unhide zero val checkbox) "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", From e3b2cc24b2ce3440c7126c197f0ba82400821b63 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 20 Nov 2025 16:12:41 +0530 Subject: [PATCH 37/38] chore: fix conflicts --- .../stock_reconciliation_item.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json index 47e59d7e9e8..e61591694e0 100644 --- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json +++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json @@ -254,11 +254,7 @@ ], "istable": 1, "links": [], -<<<<<<< HEAD - "modified": "2025-03-12 16:34:51.326821", -======= "modified": "2025-11-20 15:27:13.868179", ->>>>>>> 20e0313a8c (fix: unhide zero val checkbox) "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation Item", @@ -269,4 +265,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} From 4ba4da090dace52f0f1d49a30cad9dc0b36c625c Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 20 Nov 2025 11:49:55 +0530 Subject: [PATCH 38/38] fix(product bundle): fields reset if doc is new (cherry picked from commit 7faee7edc2c713024d12fa659a5e1da9646a35c8) --- erpnext/stock/doctype/packed_item/packed_item.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index 5a4f3e7722d..65bec2d7ea3 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -108,7 +108,12 @@ def get_indexed_packed_items_table(doc): """ indexed_table = {} for packed_item in doc.get("packed_items"): - key = (packed_item.parent_item, packed_item.item_code, packed_item.parent_detail_docname) + key = ( + packed_item.parent_item, + packed_item.item_code, + packed_item.idx if doc.is_new() else packed_item.parent_detail_docname, + ) + indexed_table[key] = packed_item return indexed_table @@ -169,7 +174,11 @@ def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, re exists, pi_row = False, {} # check if row already exists in packed items table - key = (main_item_row.item_code, packing_item.item_code, main_item_row.name) + key = ( + main_item_row.item_code, + packing_item.item_code, + main_item_row.idx if doc.is_new() else main_item_row.name, + ) if packed_items_table.get(key): pi_row, exists = packed_items_table.get(key), True