Merge pull request #39535 from ruthra-kumar/rename_advance_doctype_variable_hook

refactor: use generic name for advance doctypes variable
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 6b0a5e3..30700d0 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -240,7 +240,6 @@
 			cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
 
 	if account:
-
 		if not (frappe.flags.ignore_account_permission or ignore_account_permission):
 			acc.check_permission("read")
 
@@ -286,18 +285,22 @@
 		cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
 
 	if account or (party_type and party) or account_type:
-
+		precision = get_currency_precision()
 		if in_account_currency:
-			select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)"
+			select_field = (
+				"sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))"
+			)
 		else:
-			select_field = "sum(debit) - sum(credit)"
+			select_field = "sum(round(debit, %s)) - sum(round(credit, %s))"
+
 		bal = frappe.db.sql(
 			"""
 			SELECT {0}
 			FROM `tabGL Entry` gle
 			WHERE {1}""".format(
 				select_field, " and ".join(cond)
-			)
+			),
+			(precision, precision),
 		)[0][0]
 		# if bal is None, return 0
 		return flt(bal)
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index cc23d9d..166e8c4 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -519,14 +519,11 @@
 			movement.cancel()
 
 	def cancel_capitalization(self):
-		asset_capitalization = frappe.db.get_value(
-			"Asset Capitalization",
-			{"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"},
-		)
-
-		if asset_capitalization:
-			asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization)
-			asset_capitalization.cancel()
+		if self.capitalized_in:
+			self.db_set("capitalized_in", None)
+			asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in)
+			if asset_capitalization.docstatus == 1:
+				asset_capitalization.cancel()
 
 	def delete_depreciation_entries(self):
 		if self.calculate_depreciation:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index a93af94..df4593b 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -561,6 +561,8 @@
 def reverse_depreciation_entry_made_after_disposal(asset, date):
 	for row in asset.get("finance_books"):
 		asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
+		if not asset_depr_schedule_doc:
+			continue
 
 		for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
 			if schedule.schedule_date == date:
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index cad74df..5e251a5 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -146,6 +146,7 @@
 	def cancel_target_asset(self):
 		if self.entry_type == "Capitalization" and self.target_asset:
 			asset_doc = frappe.get_doc("Asset", self.target_asset)
+			frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
 			if asset_doc.docstatus == 1:
 				asset_doc.cancel()
 
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e7f0fe8..1ed719d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -22,6 +22,7 @@
 	get_link_to_form,
 	getdate,
 	nowdate,
+	parse_json,
 	today,
 )
 
@@ -833,6 +834,37 @@
 
 			self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
 
+	def append_taxes_from_item_tax_template(self):
+		if not frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
+			return
+
+		for row in self.items:
+			item_tax_rate = row.get("item_tax_rate")
+			if not item_tax_rate:
+				continue
+
+			if isinstance(item_tax_rate, str):
+				item_tax_rate = parse_json(item_tax_rate)
+
+			for account_head, rate in item_tax_rate.items():
+				row = self.get_tax_row(account_head)
+
+				if not row:
+					self.append(
+						"taxes",
+						{
+							"charge_type": "On Net Total",
+							"account_head": account_head,
+							"rate": 0,
+							"description": account_head,
+						},
+					)
+
+	def get_tax_row(self, account_head):
+		for row in self.taxes:
+			if row.account_head == account_head:
+				return row
+
 	def set_other_charges(self):
 		self.set("taxes", [])
 		self.set_taxes()
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 44a4957..80ade70 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -71,6 +71,10 @@
 		let warehouse = this.item?.type_of_transaction === "Outward" ?
 			(this.item.warehouse || this.item.s_warehouse) : "";
 
+		if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
+			warehouse = this.get_warehouse();
+		}
+
 		return {
 			'item_code': this.item.item_code,
 			'warehouse': ["=", warehouse]
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 2a4855e..86c7a72 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -609,6 +609,61 @@
 		quotation.items[0].conversion_factor = 2.23
 		self.assertRaises(frappe.ValidationError, quotation.save)
 
+	def test_item_tax_template_for_quotation(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		if not frappe.db.exists("Account", {"account_name": "_Test Vat", "company": "_Test Company"}):
+			frappe.get_doc(
+				{
+					"doctype": "Account",
+					"account_name": "_Test Vat",
+					"company": "_Test Company",
+					"account_type": "Tax",
+					"root_type": "Asset",
+					"is_group": 0,
+					"parent_account": "Tax Assets - _TC",
+					"tax_rate": 10,
+				}
+			).insert()
+
+		if not frappe.db.exists("Item Tax Template", "Vat Template - _TC"):
+			doc = frappe.get_doc(
+				{
+					"doctype": "Item Tax Template",
+					"name": "Vat Template",
+					"title": "Vat Template",
+					"company": "_Test Company",
+					"taxes": [
+						{
+							"tax_type": "_Test Vat - _TC",
+							"tax_rate": 5,
+						}
+					],
+				}
+			).insert()
+
+		item_doc = make_item("_Test Item Tax Template QTN", {"is_stock_item": 1})
+		if not frappe.db.exists(
+			"Item Tax", {"parent": item_doc.name, "item_tax_template": "Vat Template - _TC"}
+		):
+			item_doc.append("taxes", {"item_tax_template": "Vat Template - _TC"})
+			item_doc.save()
+
+		quotation = make_quotation(
+			item_code="_Test Item Tax Template QTN", qty=1, rate=100, do_not_submit=1
+		)
+		self.assertFalse(quotation.taxes)
+
+		quotation.append_taxes_from_item_tax_template()
+		quotation.save()
+		self.assertTrue(quotation.taxes)
+		for row in quotation.taxes:
+			self.assertEqual(row.account_head, "_Test Vat - _TC")
+			self.assertAlmostEqual(row.base_tax_amount, quotation.total * 5 / 100)
+
+		item_doc.taxes = []
+		item_doc.save()
+
 
 test_records = frappe.get_test_records("Quotation")
 
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index 00acc80..72b7fa2 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -210,7 +210,6 @@
 		.where(
 			(so.docstatus == 1)
 			& (so.status.isin(["To Deliver and Bill", "To Bill", "To Pay"]))
-			& (so.payment_terms_template != "NULL")
 			& (so.company == conditions.company)
 			& (so.transaction_date[conditions.start_date : conditions.end_date])
 		)
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 68a3854..876b6a4 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -908,8 +908,8 @@
 @frappe.whitelist()
 def is_deletion_job_running(company):
 	job_id = generate_id_for_deletion_job(company)
-	job_name = get_job(job_id).get_id()  # job name will have site prefix
 	if is_job_enqueued(job_id):
+		job_name = get_job(job_id).get_id()  # job name will have site prefix
 		frappe.throw(
 			_("A Transaction Deletion Job: {0} is already running for {1}").format(
 				frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company)
diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
index 319d435..844e786 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
@@ -4,9 +4,10 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 
-class TestTransactionDeletionRecord(unittest.TestCase):
+class TestTransactionDeletionRecord(FrappeTestCase):
 	def setUp(self):
 		create_company("Dunder Mifflin Paper Co")
 
@@ -14,7 +15,7 @@
 		frappe.db.rollback()
 
 	def test_doctypes_contain_company_field(self):
-		tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co")
+		tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
 		for doctype in tdr.doctypes:
 			contains_company = False
 			doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()["fields"]
@@ -27,17 +28,27 @@
 	def test_no_of_docs_is_correct(self):
 		for i in range(5):
 			create_task("Dunder Mifflin Paper Co")
-		tdr = create_transaction_deletion_request("Dunder Mifflin Paper Co")
+		tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
 		for doctype in tdr.doctypes:
 			if doctype.doctype_name == "Task":
 				self.assertEqual(doctype.no_of_docs, 5)
 
 	def test_deletion_is_successful(self):
 		create_task("Dunder Mifflin Paper Co")
-		create_transaction_deletion_request("Dunder Mifflin Paper Co")
+		create_transaction_deletion_doc("Dunder Mifflin Paper Co")
 		tasks_containing_company = frappe.get_all("Task", filters={"company": "Dunder Mifflin Paper Co"})
 		self.assertEqual(tasks_containing_company, [])
 
+	def test_company_transaction_deletion_request(self):
+		from erpnext.setup.doctype.company.company import create_transaction_deletion_request
+
+		# don't reuse below company for other test cases
+		company = "Deep Space Exploration"
+		create_company(company)
+
+		# below call should not raise any exceptions or throw errors
+		create_transaction_deletion_request(company)
+
 
 def create_company(company_name):
 	company = frappe.get_doc(
@@ -46,7 +57,7 @@
 	company.insert(ignore_if_duplicate=True)
 
 
-def create_transaction_deletion_request(company):
+def create_transaction_deletion_doc(company):
 	tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
 	tdr.insert()
 	tdr.submit()
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 3e44049..48397a3 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -774,6 +774,62 @@
 		self.assertEqual(mr.per_ordered, 100)
 		self.assertEqual(existing_requested_qty, current_requested_qty)
 
+	def test_auto_email_users_with_company_user_permissions(self):
+		from erpnext.stock.reorder_item import get_email_list
+
+		comapnywise_users = {
+			"_Test Company": "test_auto_email_@example.com",
+			"_Test Company 1": "test_auto_email_1@example.com",
+		}
+
+		permissions = []
+
+		for company, user in comapnywise_users.items():
+			if not frappe.db.exists("User", user):
+				frappe.get_doc(
+					{
+						"doctype": "User",
+						"email": user,
+						"first_name": user,
+						"send_notifications": 0,
+						"enabled": 1,
+						"user_type": "System User",
+						"roles": [{"role": "Purchase Manager"}],
+					}
+				).insert(ignore_permissions=True)
+
+			if not frappe.db.exists(
+				"User Permission", {"user": user, "allow": "Company", "for_value": company}
+			):
+				perm_doc = frappe.get_doc(
+					{
+						"doctype": "User Permission",
+						"user": user,
+						"allow": "Company",
+						"for_value": company,
+						"apply_to_all_doctypes": 1,
+					}
+				).insert(ignore_permissions=True)
+
+				permissions.append(perm_doc)
+
+		comapnywise_mr_list = frappe._dict({})
+		mr1 = make_material_request()
+		comapnywise_mr_list.setdefault(mr1.company, []).append(mr1.name)
+
+		mr2 = make_material_request(
+			company="_Test Company 1", warehouse="Stores - _TC1", cost_center="Main - _TC1"
+		)
+		comapnywise_mr_list.setdefault(mr2.company, []).append(mr2.name)
+
+		for company, mr_list in comapnywise_mr_list.items():
+			emails = get_email_list(company)
+
+			self.assertTrue(comapnywise_users[company] in emails)
+
+		for perm in permissions:
+			perm.delete()
+
 
 def get_in_transit_warehouse(company):
 	if not frappe.db.exists("Warehouse Type", "Transit"):
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 63cc938..9cad8f6 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
@@ -250,6 +250,7 @@
 
 		for d in self.entries:
 			available_qty = 0
+
 			if self.has_serial_no:
 				d.incoming_rate = abs(sn_obj.serial_no_incoming_rate.get(d.serial_no, 0.0))
 			else:
@@ -892,6 +893,13 @@
 		elif batch_nos:
 			self.set("entries", batch_nos)
 
+	def delete_serial_batch_entries(self):
+		SBBE = frappe.qb.DocType("Serial and Batch Entry")
+
+		frappe.qb.from_(SBBE).delete().where(SBBE.parent == self.name).run()
+
+		self.set("entries", [])
+
 
 @frappe.whitelist()
 def download_blank_csv_template(content):
@@ -1374,10 +1382,12 @@
 	elif kwargs.based_on == "Expiry":
 		order_by = "amc_expiry_date asc"
 
-	filters = {"item_code": kwargs.item_code, "warehouse": ("is", "set")}
+	filters = {"item_code": kwargs.item_code}
 
-	if kwargs.warehouse:
-		filters["warehouse"] = kwargs.warehouse
+	if not kwargs.get("ignore_warehouse"):
+		filters["warehouse"] = ("is", "set")
+		if kwargs.warehouse:
+			filters["warehouse"] = kwargs.warehouse
 
 	# Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos.
 	ignore_serial_nos = get_reserved_serial_nos(kwargs)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 6819968..788ae0d 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -156,6 +156,7 @@
 							"warehouse": item.warehouse,
 							"posting_date": self.posting_date,
 							"posting_time": self.posting_time,
+							"ignore_warehouse": 1,
 						}
 					)
 				)
@@ -780,7 +781,20 @@
 
 			current_qty = 0.0
 			if row.current_serial_and_batch_bundle:
-				current_qty = self.get_qty_for_serial_and_batch_bundle(row)
+				current_qty = self.get_current_qty_for_serial_or_batch(row)
+			elif row.serial_no:
+				item_dict = get_stock_balance_for(
+					row.item_code,
+					row.warehouse,
+					self.posting_date,
+					self.posting_time,
+					voucher_no=self.name,
+				)
+
+				current_qty = item_dict.get("qty")
+				row.current_serial_no = item_dict.get("serial_nos")
+				row.current_valuation_rate = item_dict.get("rate")
+				val_rate = item_dict.get("rate")
 			elif row.batch_no:
 				current_qty = get_batch_qty_for_stock_reco(
 					row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name
@@ -788,15 +802,16 @@
 
 			precesion = row.precision("current_qty")
 			if flt(current_qty, precesion) != flt(row.current_qty, precesion):
-				val_rate = get_valuation_rate(
-					row.item_code,
-					row.warehouse,
-					self.doctype,
-					self.name,
-					company=self.company,
-					batch_no=row.batch_no,
-					serial_and_batch_bundle=row.current_serial_and_batch_bundle,
-				)
+				if not row.serial_no:
+					val_rate = get_valuation_rate(
+						row.item_code,
+						row.warehouse,
+						self.doctype,
+						self.name,
+						company=self.company,
+						batch_no=row.batch_no,
+						serial_and_batch_bundle=row.current_serial_and_batch_bundle,
+					)
 
 				row.current_valuation_rate = val_rate
 				row.current_qty = current_qty
@@ -842,11 +857,56 @@
 
 		return allow_negative_stock
 
-	def get_qty_for_serial_and_batch_bundle(self, row):
+	def get_current_qty_for_serial_or_batch(self, row):
 		doc = frappe.get_doc("Serial and Batch Bundle", row.current_serial_and_batch_bundle)
-		precision = doc.entries[0].precision("qty")
+		current_qty = 0.0
+		if doc.has_serial_no:
+			current_qty = self.get_current_qty_for_serial_nos(doc)
+		elif doc.has_batch_no:
+			current_qty = self.get_current_qty_for_batch_nos(doc)
 
-		current_qty = 0
+		return abs(current_qty)
+
+	def get_current_qty_for_serial_nos(self, doc):
+		serial_nos_details = get_available_serial_nos(
+			frappe._dict(
+				{
+					"item_code": doc.item_code,
+					"warehouse": doc.warehouse,
+					"posting_date": self.posting_date,
+					"posting_time": self.posting_time,
+					"voucher_no": self.name,
+					"ignore_warehouse": 1,
+				}
+			)
+		)
+
+		if not serial_nos_details:
+			return 0.0
+
+		doc.delete_serial_batch_entries()
+		current_qty = 0.0
+		for serial_no_row in serial_nos_details:
+			current_qty += 1
+			doc.append(
+				"entries",
+				{
+					"serial_no": serial_no_row.serial_no,
+					"qty": -1,
+					"warehouse": doc.warehouse,
+					"batch_no": serial_no_row.batch_no,
+				},
+			)
+
+		doc.set_incoming_rate(save=True)
+		doc.calculate_qty_and_amount(save=True)
+		doc.db_update_all()
+
+		return current_qty
+
+	def get_current_qty_for_batch_nos(self, doc):
+		current_qty = 0.0
+		precision = doc.entries[0].precision("qty")
 		for d in doc.entries:
 			qty = (
 				get_batch_qty(
@@ -864,7 +924,7 @@
 
 			current_qty += qty
 
-		return abs(current_qty)
+		return current_qty
 
 
 def get_batch_qty_for_stock_reco(
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 70e9fb2..0bbfed4 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -925,6 +925,74 @@
 
 			self.assertEqual(len(serial_batch_bundle), 0)
 
+	def test_backdated_purchase_receipt_with_stock_reco(self):
+		item_code = self.make_item(
+			properties={
+				"is_stock_item": 1,
+				"has_serial_no": 1,
+				"serial_no_series": "TEST-SERIAL-.###",
+			}
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+
+		# Step - 1: Create a Backdated Purchase Receipt
+
+		pr1 = make_purchase_receipt(
+			item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
+		)
+		pr1.reload()
+
+		serial_nos = sorted(get_serial_nos_from_bundle(pr1.items[0].serial_and_batch_bundle))[:5]
+
+		# Step - 2: Create a Stock Reconciliation
+		sr1 = create_stock_reconciliation(
+			item_code=item_code,
+			warehouse=warehouse,
+			qty=5,
+			serial_no=serial_nos,
+		)
+
+		data = frappe.get_all(
+			"Stock Ledger Entry",
+			fields=["serial_no", "actual_qty", "stock_value_difference"],
+			filters={"voucher_no": sr1.name, "is_cancelled": 0},
+			order_by="creation",
+		)
+
+		for d in data:
+			if d.actual_qty < 0:
+				self.assertEqual(d.actual_qty, -10.0)
+				self.assertAlmostEqual(d.stock_value_difference, -1000.0)
+			else:
+				self.assertEqual(d.actual_qty, 5.0)
+				self.assertAlmostEqual(d.stock_value_difference, 500.0)
+
+		# Step - 3: Create a Purchase Receipt before the first Purchase Receipt
+		make_purchase_receipt(
+			item_code=item_code, warehouse=warehouse, qty=10, rate=200, posting_date=add_days(nowdate(), -5)
+		)
+
+		data = frappe.get_all(
+			"Stock Ledger Entry",
+			fields=["serial_no", "actual_qty", "stock_value_difference"],
+			filters={"voucher_no": sr1.name, "is_cancelled": 0},
+			order_by="creation",
+		)
+
+		for d in data:
+			if d.actual_qty < 0:
+				self.assertEqual(d.actual_qty, -20.0)
+				self.assertAlmostEqual(d.stock_value_difference, -3000.0)
+			else:
+				self.assertEqual(d.actual_qty, 5.0)
+				self.assertAlmostEqual(d.stock_value_difference, 500.0)
+
+		active_serial_no = frappe.get_all(
+			"Serial No", filters={"status": "Active", "item_code": item_code}
+		)
+		self.assertEqual(len(active_serial_no), 5)
+
 
 def create_batch_item_with_batch(item_name, batch_id):
 	batch_item_doc = create_item(item_name, is_stock_item=1)
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 1f5f41a..276531a 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -145,6 +145,7 @@
 
 		mr.log_error("Unable to create material request")
 
+	company_wise_mr = frappe._dict({})
 	for request_type in material_requests:
 		for company in material_requests[request_type]:
 			try:
@@ -206,17 +207,19 @@
 				mr.submit()
 				mr_list.append(mr)
 
+				company_wise_mr.setdefault(company, []).append(mr)
+
 			except Exception:
 				_log_exception(mr)
 
-	if mr_list:
+	if company_wise_mr:
 		if getattr(frappe.local, "reorder_email_notify", None) is None:
 			frappe.local.reorder_email_notify = cint(
 				frappe.db.get_single_value("Stock Settings", "reorder_email_notify")
 			)
 
 		if frappe.local.reorder_email_notify:
-			send_email_notification(mr_list)
+			send_email_notification(company_wise_mr)
 
 	if exceptions_list:
 		notify_errors(exceptions_list)
@@ -224,20 +227,56 @@
 	return mr_list
 
 
-def send_email_notification(mr_list):
+def send_email_notification(company_wise_mr):
 	"""Notify user about auto creation of indent"""
 
-	email_list = frappe.db.sql_list(
-		"""select distinct r.parent
-		from `tabHas Role` r, tabUser p
-		where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
-		and r.role in ('Purchase Manager','Stock Manager')
-		and p.name not in ('Administrator', 'All', 'Guest')"""
+	for company, mr_list in company_wise_mr.items():
+		email_list = get_email_list(company)
+
+		if not email_list:
+			continue
+
+		msg = frappe.render_template("templates/emails/reorder_item.html", {"mr_list": mr_list})
+
+		frappe.sendmail(
+			recipients=email_list, subject=_("Auto Material Requests Generated"), message=msg
+		)
+
+
+def get_email_list(company):
+	users = get_comapny_wise_users(company)
+	user_table = frappe.qb.DocType("User")
+	role_table = frappe.qb.DocType("Has Role")
+
+	query = (
+		frappe.qb.from_(user_table)
+		.inner_join(role_table)
+		.on(user_table.name == role_table.parent)
+		.select(user_table.email)
+		.where(
+			(role_table.role.isin(["Purchase Manager", "Stock Manager"]))
+			& (user_table.name.notin(["Administrator", "All", "Guest"]))
+			& (user_table.enabled == 1)
+			& (user_table.docstatus < 2)
+		)
 	)
 
-	msg = frappe.render_template("templates/emails/reorder_item.html", {"mr_list": mr_list})
+	if users:
+		query = query.where(user_table.name.isin(users))
 
-	frappe.sendmail(recipients=email_list, subject=_("Auto Material Requests Generated"), message=msg)
+	emails = query.run(as_dict=True)
+
+	return list(set([email.email for email in emails]))
+
+
+def get_comapny_wise_users(company):
+	users = frappe.get_all(
+		"User Permission",
+		filters={"allow": "Company", "for_value": company, "apply_to_all_doctypes": 1},
+		fields=["user"],
+	)
+
+	return [user.user for user in users]
 
 
 def notify_errors(exceptions_list):
diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js
index 6de5f00..fe6e83e 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.js
+++ b/erpnext/stock/report/stock_balance/stock_balance.js
@@ -99,7 +99,7 @@
 			"fieldname": 'ignore_closing_balance',
 			"label": __('Ignore Closing Balance'),
 			"fieldtype": 'Check',
-			"default": 1
+			"default": 0
 		},
 	],
 
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 0370666..45764f3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -9,9 +9,18 @@
 import frappe
 from frappe import _, scrub
 from frappe.model.meta import get_field_precision
-from frappe.query_builder import Case
 from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json
+from frappe.utils import (
+	cint,
+	cstr,
+	flt,
+	get_link_to_form,
+	getdate,
+	now,
+	nowdate,
+	nowtime,
+	parse_json,
+)
 
 import erpnext
 from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
@@ -712,11 +721,10 @@
 
 		if (
 			sle.voucher_type == "Stock Reconciliation"
-			and (
-				sle.batch_no or (sle.has_batch_no and sle.serial_and_batch_bundle and not sle.has_serial_no)
-			)
+			and (sle.batch_no or sle.serial_no or sle.serial_and_batch_bundle)
 			and sle.voucher_detail_no
 			and not self.args.get("sle_id")
+			and sle.is_cancelled == 0
 		):
 			self.reset_actual_qty_for_stock_reco(sle)
 
@@ -737,6 +745,23 @@
 
 		if sle.serial_and_batch_bundle:
 			self.calculate_valuation_for_serial_batch_bundle(sle)
+		elif sle.serial_no and not self.args.get("sle_id"):
+			# Only run in reposting
+			self.get_serialized_values(sle)
+			self.wh_data.qty_after_transaction += flt(sle.actual_qty)
+			if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
+				self.wh_data.qty_after_transaction = sle.qty_after_transaction
+
+			self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
+				self.wh_data.valuation_rate
+			)
+		elif (
+			sle.batch_no
+			and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
+			and not self.args.get("sle_id")
+		):
+			# Only run in reposting
+			self.update_batched_values(sle)
 		else:
 			if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no and not has_dimensions:
 				# assert
@@ -782,6 +807,45 @@
 		):
 			self.update_outgoing_rate_on_transaction(sle)
 
+	def get_serialized_values(self, sle):
+		incoming_rate = flt(sle.incoming_rate)
+		actual_qty = flt(sle.actual_qty)
+		serial_nos = cstr(sle.serial_no).split("\n")
+
+		if incoming_rate < 0:
+			# wrong incoming rate
+			incoming_rate = self.wh_data.valuation_rate
+
+		stock_value_change = 0
+		if actual_qty > 0:
+			stock_value_change = actual_qty * incoming_rate
+		else:
+			# In case of delivery/stock issue, get average purchase rate
+			# of serial nos of current entry
+			if not sle.is_cancelled:
+				outgoing_value = self.get_incoming_value_for_serial_nos(sle, serial_nos)
+				stock_value_change = -1 * outgoing_value
+			else:
+				stock_value_change = actual_qty * sle.outgoing_rate
+
+		new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
+
+		if new_stock_qty > 0:
+			new_stock_value = (
+				self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
+			) + stock_value_change
+			if new_stock_value >= 0:
+				# calculate new valuation rate only if stock value is positive
+				# else it remains the same as that of previous entry
+				self.wh_data.valuation_rate = new_stock_value / new_stock_qty
+
+		if not self.wh_data.valuation_rate and sle.voucher_detail_no:
+			allow_zero_rate = self.check_if_allow_zero_valuation_rate(
+				sle.voucher_type, sle.voucher_detail_no
+			)
+			if not allow_zero_rate:
+				self.wh_data.valuation_rate = self.get_fallback_rate(sle)
+
 	def reset_actual_qty_for_stock_reco(self, sle):
 		doc = frappe.get_cached_doc("Stock Reconciliation", sle.voucher_no)
 		doc.recalculate_current_qty(sle.voucher_detail_no, sle.creation, sle.actual_qty > 0)
@@ -795,6 +859,36 @@
 			if abs(sle.actual_qty) == 0.0:
 				sle.is_cancelled = 1
 
+		if sle.serial_and_batch_bundle and frappe.get_cached_value(
+			"Item", sle.item_code, "has_serial_no"
+		):
+			self.update_serial_no_status(sle)
+
+	def update_serial_no_status(self, sle):
+		from erpnext.stock.serial_batch_bundle import get_serial_nos
+
+		serial_nos = get_serial_nos(sle.serial_and_batch_bundle)
+		if not serial_nos:
+			return
+
+		warehouse = None
+		status = "Inactive"
+
+		if sle.actual_qty > 0:
+			warehouse = sle.warehouse
+			status = "Active"
+
+		sn_table = frappe.qb.DocType("Serial No")
+
+		query = (
+			frappe.qb.update(sn_table)
+			.set(sn_table.warehouse, warehouse)
+			.set(sn_table.status, status)
+			.where(sn_table.name.isin(serial_nos))
+		)
+
+		query.run()
+
 	def calculate_valuation_for_serial_batch_bundle(self, sle):
 		doc = frappe.get_cached_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
 
@@ -1171,11 +1265,12 @@
 			outgoing_rate = get_batch_incoming_rate(
 				item_code=sle.item_code,
 				warehouse=sle.warehouse,
-				serial_and_batch_bundle=sle.serial_and_batch_bundle,
+				batch_no=sle.batch_no,
 				posting_date=sle.posting_date,
 				posting_time=sle.posting_time,
 				creation=sle.creation,
 			)
+
 			if outgoing_rate is None:
 				# This can *only* happen if qty available for the batch is zero.
 				# in such case fall back various other rates.
@@ -1449,11 +1544,10 @@
 
 
 def get_batch_incoming_rate(
-	item_code, warehouse, serial_and_batch_bundle, posting_date, posting_time, creation=None
+	item_code, warehouse, batch_no, posting_date, posting_time, creation=None
 ):
 
 	sle = frappe.qb.DocType("Stock Ledger Entry")
-	batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
 
 	timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
 		posting_date, posting_time
@@ -1464,28 +1558,13 @@
 			== CombineDatetime(posting_date, posting_time)
 		) & (sle.creation < creation)
 
-	batches = frappe.get_all(
-		"Serial and Batch Entry", fields=["batch_no"], filters={"parent": serial_and_batch_bundle}
-	)
-
 	batch_details = (
 		frappe.qb.from_(sle)
-		.inner_join(batch_ledger)
-		.on(sle.serial_and_batch_bundle == batch_ledger.parent)
-		.select(
-			Sum(
-				Case()
-				.when(sle.actual_qty > 0, batch_ledger.qty * batch_ledger.incoming_rate)
-				.else_(batch_ledger.qty * batch_ledger.outgoing_rate * -1)
-			).as_("batch_value"),
-			Sum(Case().when(sle.actual_qty > 0, batch_ledger.qty).else_(batch_ledger.qty * -1)).as_(
-				"batch_qty"
-			),
-		)
+		.select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
 		.where(
 			(sle.item_code == item_code)
 			& (sle.warehouse == warehouse)
-			& (batch_ledger.batch_no.isin([row.batch_no for row in batches]))
+			& (sle.batch_no == batch_no)
 			& (sle.is_cancelled == 0)
 		)
 		.where(timestamp_condition)
diff --git a/erpnext/utilities/web_form/addresses/addresses.json b/erpnext/utilities/web_form/addresses/addresses.json
index 2f5e180..4e2d8e3 100644
--- a/erpnext/utilities/web_form/addresses/addresses.json
+++ b/erpnext/utilities/web_form/addresses/addresses.json
@@ -8,26 +8,29 @@
  "allow_print": 0,
  "amount": 0.0,
  "amount_based_on_field": 0,
+ "anonymous": 0,
+ "apply_document_permissions": 1,
+ "condition_json": "[]",
  "creation": "2016-06-24 15:50:33.196990",
  "doc_type": "Address",
  "docstatus": 0,
  "doctype": "Web Form",
  "idx": 0,
  "is_standard": 1,
+ "list_columns": [],
+ "list_title": "",
  "login_required": 1,
  "max_attachment_size": 0,
- "modified": "2019-10-15 06:55:30.405119",
- "modified_by": "Administrator",
+ "modified": "2024-01-24 10:28:35.026064",
+ "modified_by": "rohitw1991@gmail.com",
  "module": "Utilities",
  "name": "addresses",
  "owner": "Administrator",
  "published": 1,
  "route": "address",
- "route_to_success_link": 0,
  "show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 1,
  "show_sidebar": 0,
- "sidebar_items": [],
  "success_url": "/addresses",
  "title": "Address",
  "web_form_fields": [