Merge branch 'develop' into stock-reservation
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 3f985b6..c0eed18 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -31,6 +31,7 @@
   "determine_address_tax_category_from",
   "column_break_19",
   "add_taxes_from_item_tax_template",
+  "book_tax_discount_loss",
   "print_settings",
   "show_inclusive_tax_in_print",
   "column_break_12",
@@ -360,6 +361,13 @@
    "fieldname": "show_balance_in_coa",
    "fieldtype": "Check",
    "label": "Show Balances in Chart Of Accounts"
+  },
+  {
+   "default": "0",
+   "description": "Split Early Payment Discount Loss into Income and Tax Loss",
+   "fieldname": "book_tax_discount_loss",
+   "fieldtype": "Check",
+   "label": "Book Tax Loss on Early Payment Discount"
   }
  ],
  "icon": "icon-cog",
@@ -367,7 +375,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-01-02 12:07:42.434214",
+ "modified": "2023-03-28 09:50:20.375233",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 80878ac..0817187 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -81,7 +81,7 @@
 
 		loan_disbursement = frappe.qb.DocType("Loan Disbursement")
 
-		loan_disbursements = (
+		query = (
 			frappe.qb.from_(loan_disbursement)
 			.select(
 				ConstantColumn("Loan Disbursement").as_("payment_document"),
@@ -90,17 +90,22 @@
 				ConstantColumn(0).as_("debit"),
 				loan_disbursement.reference_number.as_("cheque_number"),
 				loan_disbursement.reference_date.as_("cheque_date"),
+				loan_disbursement.clearance_date.as_("clearance_date"),
 				loan_disbursement.disbursement_date.as_("posting_date"),
 				loan_disbursement.applicant.as_("against_account"),
 			)
 			.where(loan_disbursement.docstatus == 1)
 			.where(loan_disbursement.disbursement_date >= self.from_date)
 			.where(loan_disbursement.disbursement_date <= self.to_date)
-			.where(loan_disbursement.clearance_date.isnull())
 			.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
 			.orderby(loan_disbursement.disbursement_date)
 			.orderby(loan_disbursement.name, order=frappe.qb.desc)
-		).run(as_dict=1)
+		)
+
+		if not self.include_reconciled_entries:
+			query = query.where(loan_disbursement.clearance_date.isnull())
+
+		loan_disbursements = query.run(as_dict=1)
 
 		loan_repayment = frappe.qb.DocType("Loan Repayment")
 
@@ -113,16 +118,19 @@
 				ConstantColumn(0).as_("credit"),
 				loan_repayment.reference_number.as_("cheque_number"),
 				loan_repayment.reference_date.as_("cheque_date"),
+				loan_repayment.clearance_date.as_("clearance_date"),
 				loan_repayment.applicant.as_("against_account"),
 				loan_repayment.posting_date,
 			)
 			.where(loan_repayment.docstatus == 1)
-			.where(loan_repayment.clearance_date.isnull())
 			.where(loan_repayment.posting_date >= self.from_date)
 			.where(loan_repayment.posting_date <= self.to_date)
 			.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
 		)
 
+		if not self.include_reconciled_entries:
+			query = query.where(loan_repayment.clearance_date.isnull())
+
 		if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
 			query = query.where((loan_repayment.repay_from_salary == 0))
 
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 1516237..fcbaf32 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -46,7 +46,7 @@
 	def add_payment_entries(self, vouchers):
 		"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
 		if 0.0 >= self.unallocated_amount:
-			frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
+			frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
 
 		added = False
 		for voucher in vouchers:
@@ -114,9 +114,7 @@
 
 				elif 0.0 > unallocated_amount:
 					self.db_delete_payment_entry(payment_entry)
-					frappe.throw(
-						frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}")
-					)
+					frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
 
 		self.reload()
 
@@ -178,7 +176,9 @@
 		if gle["gl_account"] == gl_bank_account:
 			if gle["amount"] <= 0.0:
 				frappe.throw(
-					frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}")
+					frappe._("Voucher {0} value is broken: {1}").format(
+						payment_entry.payment_entry, gle["amount"]
+					)
 				)
 
 			unmatched_gles -= 1
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index cb7da17..d6e1be4 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -325,14 +325,14 @@
 
 	if template_type == "Blank Template":
 		for root_type in get_root_types():
-			writer.writerow(["", "", "", 1, "", root_type])
+			writer.writerow(["", "", "", "", 1, "", root_type])
 
 		for account in get_mandatory_group_accounts():
-			writer.writerow(["", "", "", 1, account, "Asset"])
+			writer.writerow(["", "", "", "", 1, account, "Asset"])
 
 		for account_type in get_mandatory_account_types():
 			writer.writerow(
-				["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
+				["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
 			)
 	else:
 		writer = get_sample_template(writer)
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index a4f6a74..81c2d8b 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -490,6 +490,8 @@
 		conditions.append(gl.company == company)
 		conditions.append(gl.account == account)
 		conditions.append(gl.is_cancelled == 0)
+		conditions.append((gl.debit > 0) | (gl.credit > 0))
+		conditions.append((gl.debit_in_account_currency > 0) | (gl.credit_in_account_currency > 0))
 		if party_type:
 			conditions.append(gl.party_type == party_type)
 		if party:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index db399b7..68364be 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -51,7 +51,7 @@
 		self.validate_multi_currency()
 		self.set_amounts_in_company_currency()
 		self.validate_debit_credit_amount()
-
+		self.set_total_debit_credit()
 		# Do not validate while importing via data import
 		if not frappe.flags.in_import:
 			self.validate_total_debit_and_credit()
@@ -666,7 +666,6 @@
 					frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
 
 	def validate_total_debit_and_credit(self):
-		self.set_total_debit_credit()
 		if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
 			if self.difference:
 				frappe.throw(
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 2a8e2527..f8969b8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -244,8 +244,6 @@
 		frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
 			party_account_currency, "references");
 
-		frm.set_currency_labels(["amount"], company_currency, "deductions");
-
 		cur_frm.set_df_property("source_exchange_rate", "description",
 			("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));
 
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cd5b6d5..c34bddd 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -416,7 +416,7 @@
 
 		for ref in self.get("references"):
 			if ref.payment_term and ref.reference_name:
-				key = (ref.payment_term, ref.reference_name)
+				key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
 				invoice_payment_amount_map.setdefault(key, 0.0)
 				invoice_payment_amount_map[key] += ref.allocated_amount
 
@@ -424,20 +424,37 @@
 					payment_schedule = frappe.get_all(
 						"Payment Schedule",
 						filters={"parent": ref.reference_name},
-						fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
+						fields=[
+							"paid_amount",
+							"payment_amount",
+							"payment_term",
+							"discount",
+							"outstanding",
+							"discount_type",
+						],
 					)
 					for term in payment_schedule:
-						invoice_key = (term.payment_term, ref.reference_name)
+						invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
 						invoice_paid_amount_map.setdefault(invoice_key, {})
 						invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
-						invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
-							term.discount / 100
-						)
+						if not (term.discount_type and term.discount):
+							continue
+
+						if term.discount_type == "Percentage":
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
+								term.discount / 100
+							)
+						else:
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
 
 		for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
 			if not invoice_paid_amount_map.get(key):
 				frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
 
+			allocated_amount = self.get_allocated_amount_in_transaction_currency(
+				allocated_amount, key[2], key[1]
+			)
+
 			outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
 			discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
 
@@ -472,6 +489,33 @@
 						(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
 					)
 
+	def get_allocated_amount_in_transaction_currency(
+		self, allocated_amount, reference_doctype, reference_docname
+	):
+		"""
+		Payment Entry could be in base currency while reference's payment schedule
+		is always in transaction currency.
+		E.g.
+		* SI with base=INR and currency=USD
+		* SI with payment schedule in USD
+		* PE in INR (accounting done in base currency)
+		"""
+		ref_currency, ref_exchange_rate = frappe.db.get_value(
+			reference_doctype, reference_docname, ["currency", "conversion_rate"]
+		)
+		is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
+		# PE in different currency
+		reference_is_multi_currency = self.paid_from_account_currency != ref_currency
+
+		if not (is_single_currency and reference_is_multi_currency):
+			return allocated_amount
+
+		allocated_amount = flt(
+			allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
+		)
+
+		return allocated_amount
+
 	def set_status(self):
 		if self.docstatus == 2:
 			self.status = "Cancelled"
@@ -1642,7 +1686,14 @@
 
 @frappe.whitelist()
 def get_payment_entry(
-	dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
+	dt,
+	dn,
+	party_amount=None,
+	bank_account=None,
+	bank_amount=None,
+	party_type=None,
+	payment_type=None,
+	reference_date=None,
 ):
 	reference_doc = None
 	doc = frappe.get_doc(dt, dn)
@@ -1669,8 +1720,9 @@
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
 	)
 
-	paid_amount, received_amount, discount_amount = apply_early_payment_discount(
-		paid_amount, received_amount, doc
+	reference_date = getdate(reference_date)
+	paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
+		paid_amount, received_amount, doc, party_account_currency, reference_date
 	)
 
 	pe = frappe.new_doc("Payment Entry")
@@ -1678,6 +1730,7 @@
 	pe.company = doc.company
 	pe.cost_center = doc.get("cost_center")
 	pe.posting_date = nowdate()
+	pe.reference_date = reference_date
 	pe.mode_of_payment = doc.get("mode_of_payment")
 	pe.party_type = party_type
 	pe.party = doc.get(scrub(party_type))
@@ -1718,7 +1771,7 @@
 		):
 
 			for reference in get_reference_as_per_payment_terms(
-				doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+				doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
 			):
 				pe.append("references", reference)
 		else:
@@ -1769,16 +1822,17 @@
 	if party_account and bank:
 		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
+
 		if discount_amount:
-			pe.set_gain_or_loss(
-				account_details={
-					"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
-					"cost_center": pe.cost_center
-					or frappe.get_cached_value("Company", pe.company, "cost_center"),
-					"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
-				}
+			base_total_discount_loss = 0
+			if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
+				base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
+
+			set_pending_discount_loss(
+				pe, doc, discount_amount, base_total_discount_loss, party_account_currency
 			)
-			pe.set_difference_amount()
+
+		pe.set_difference_amount()
 
 	return pe
 
@@ -1889,20 +1943,28 @@
 	return paid_amount, received_amount
 
 
-def apply_early_payment_discount(paid_amount, received_amount, doc):
+def apply_early_payment_discount(
+	paid_amount, received_amount, doc, party_account_currency, reference_date
+):
 	total_discount = 0
+	valid_discounts = []
 	eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
 	has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
+	is_multi_currency = party_account_currency != doc.company_currency
 
 	if doc.doctype in eligible_for_payments and has_payment_schedule:
 		for term in doc.payment_schedule:
-			if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
+			if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
+
 				if term.discount_type == "Percentage":
-					discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
+					grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
+					discount_amount = flt(grand_total) * (term.discount / 100)
 				else:
 					discount_amount = term.discount
 
-				discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
+				# if accounting is done in the same currency, paid_amount = received_amount
+				conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1
+				discount_amount_in_foreign_currency = discount_amount * conversion_rate
 
 				if doc.doctype == "Sales Invoice":
 					paid_amount -= discount_amount
@@ -1911,23 +1973,151 @@
 					received_amount -= discount_amount
 					paid_amount -= discount_amount_in_foreign_currency
 
+				valid_discounts.append({"type": term.discount_type, "discount": term.discount})
 				total_discount += discount_amount
 
 		if total_discount:
-			money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
+			currency = doc.get("currency") if is_multi_currency else doc.company_currency
+			money = frappe.utils.fmt_money(total_discount, currency=currency)
 			frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
 
-	return paid_amount, received_amount, total_discount
+	return paid_amount, received_amount, total_discount, valid_discounts
+
+
+def set_pending_discount_loss(
+	pe, doc, discount_amount, base_total_discount_loss, party_account_currency
+):
+	# If multi-currency, get base discount amount to adjust with base currency deductions/losses
+	if party_account_currency != doc.company_currency:
+		discount_amount = discount_amount * doc.get("conversion_rate", 1)
+
+	# Avoid considering miniscule losses
+	discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total"))
+
+	# Set base discount amount (discount loss/pending rounding loss) in deductions
+	if discount_amount > 0.0:
+		positive_negative = -1 if pe.payment_type == "Pay" else 1
+
+		# If tax loss booking is enabled, pending loss will be rounding loss.
+		# Otherwise it will be the total discount loss.
+		book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
+		account_type = "round_off_account" if book_tax_loss else "default_discount_account"
+
+		pe.set_gain_or_loss(
+			account_details={
+				"account": frappe.get_cached_value("Company", pe.company, account_type),
+				"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+				"amount": discount_amount * positive_negative,
+			}
+		)
+
+
+def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
+	"""Split early payment discount into Income Loss & Tax Loss."""
+	total_discount_percent = get_total_discount_percent(doc, valid_discounts)
+
+	if not total_discount_percent:
+		return 0.0
+
+	base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
+	base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
+
+	# Round off total loss rather than individual losses to reduce rounding error
+	return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total"))
+
+
+def get_total_discount_percent(doc, valid_discounts) -> float:
+	"""Get total percentage and amount discount applied as a percentage."""
+	total_discount_percent = (
+		sum(
+			discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
+		)
+		or 0.0
+	)
+
+	# Operate in percentages only as it makes the income & tax split easier
+	total_discount_amount = (
+		sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
+		or 0.0
+	)
+
+	if total_discount_amount:
+		discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
+		total_discount_percent += discount_percentage
+		return total_discount_percent
+
+	return total_discount_percent
+
+
+def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
+	"""Add loss on income discount in base currency."""
+	precision = doc.precision("total")
+	base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
+
+	pe.append(
+		"deductions",
+		{
+			"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
+			"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+			"amount": flt(base_loss_on_income, precision),
+		},
+	)
+
+	return base_loss_on_income  # Return loss without rounding
+
+
+def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
+	"""Add loss on tax discount in base currency."""
+	tax_discount_loss = {}
+	base_total_tax_loss = 0
+	precision = doc.precision("tax_amount_after_discount_amount", "taxes")
+
+	# The same account head could be used more than once
+	for tax in doc.get("taxes", []):
+		base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
+			total_discount_percentage / 100
+		)
+
+		account = tax.get("account_head")
+		if not tax_discount_loss.get(account):
+			tax_discount_loss[account] = base_tax_loss
+		else:
+			tax_discount_loss[account] += base_tax_loss
+
+	for account, loss in tax_discount_loss.items():
+		base_total_tax_loss += loss
+		if loss == 0.0:
+			continue
+
+		pe.append(
+			"deductions",
+			{
+				"account": account,
+				"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+				"amount": flt(loss, precision),
+			},
+		)
+
+	return base_total_tax_loss  # Return loss without rounding
 
 
 def get_reference_as_per_payment_terms(
-	payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+	payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
 ):
 	references = []
+	is_multi_currency_acc = (doc.currency != doc.company_currency) and (
+		party_account_currency != doc.company_currency
+	)
+
 	for payment_term in payment_schedule:
 		payment_term_outstanding = flt(
 			payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
 		)
+		if not is_multi_currency_acc:
+			# If accounting is done in company currency for multi-currency transaction
+			payment_term_outstanding = flt(
+				payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
+			)
 
 		if payment_term_outstanding:
 			references.append(
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 123b5df..67049c4 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -5,7 +5,7 @@
 
 import frappe
 from frappe import qb
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import flt, nowdate
 
 from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -256,10 +256,25 @@
 			},
 		)
 		si.save()
-
 		si.submit()
 
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
+		pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+
+		self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount")
+		self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe_with_tax_loss.paid_amount, 212.4)
+		self.assertEqual(pe_with_tax_loss.deductions[0].amount, 20.0)  # Loss on Income
+		self.assertEqual(pe_with_tax_loss.deductions[1].amount, 3.6)  # Loss on Tax
+		self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
+
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
 		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 212.4)
+		self.assertEqual(pe.deductions[0].amount, 23.6)
+
 		pe.submit()
 		si.load_from_db()
 
@@ -269,6 +284,190 @@
 		self.assertEqual(si.payment_schedule[0].outstanding, 0)
 		self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
 
+	def test_payment_entry_against_payment_terms_with_discount_amount(self):
+		si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+
+		si.payment_terms_template = "Test Discount Amount Template"
+		create_payment_terms_template_with_discount(
+			name="30 Credit Days with Rs.50 Discount",
+			discount_type="Amount",
+			discount=50,
+			template_name="Test Discount Amount Template",
+		)
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+
+		si.append(
+			"taxes",
+			{
+				"charge_type": "On Net Total",
+				"account_head": "_Test Account Service Tax - _TC",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "Service Tax",
+				"rate": 18,
+			},
+		)
+		si.save()
+		si.submit()
+
+		# Set reference date past discount cut off date
+		pe_1 = get_payment_entry(
+			"Sales Invoice",
+			si.name,
+			bank_account="_Test Cash - _TC",
+			reference_date=frappe.utils.add_days(si.posting_date, 2),
+		)
+		self.assertEqual(pe_1.paid_amount, 236.0)  # discount not applied
+
+		# Test if tax loss is booked on enabling configuration
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
+		pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		self.assertEqual(pe_with_tax_loss.deductions[0].amount, 42.37)  # Loss on Income
+		self.assertEqual(pe_with_tax_loss.deductions[1].amount, 7.63)  # Loss on Tax
+		self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
+
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 186)
+		self.assertEqual(pe.deductions[0].amount, 50.0)
+
+		pe.submit()
+		si.load_from_db()
+
+		self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
+		self.assertEqual(si.payment_schedule[0].paid_amount, 186)
+		self.assertEqual(si.payment_schedule[0].outstanding, 0)
+		self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
+
+	@change_settings(
+		"Accounts Settings",
+		{
+			"allow_multi_currency_invoices_against_single_party_account": 1,
+			"book_tax_discount_loss": 1,
+		},
+	)
+	def test_payment_entry_multicurrency_si_with_base_currency_accounting_early_payment_discount(
+		self,
+	):
+		"""
+		1. Multi-currency SI with single currency accounting (company currency)
+		2. PE with early payment discount
+		3. Test if Paid Amount is calculated in company currency
+		4. Test if deductions are calculated in company currency
+
+		SI is in USD to document agreed amounts that are in USD, but the accounting is in base currency.
+		"""
+		si = create_sales_invoice(
+			customer="_Test Customer",
+			currency="USD",
+			conversion_rate=50,
+			do_not_save=1,
+		)
+		create_payment_terms_template_with_discount()
+		si.payment_terms_template = "Test Discount Template"
+
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+		si.save()
+		si.submit()
+
+		pe = get_payment_entry(
+			"Sales Invoice",
+			si.name,
+			bank_account="_Test Bank - _TC",
+		)
+		pe.reference_no = si.name
+		pe.reference_date = nowdate()
+
+		# Early payment discount loss on income
+		self.assertEqual(pe.paid_amount, 4500.0)  # Amount in company currency
+		self.assertEqual(pe.received_amount, 4500.0)
+		self.assertEqual(pe.deductions[0].amount, 500.0)
+		self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
+		self.assertEqual(pe.difference_amount, 0.0)
+
+		pe.insert()
+		pe.submit()
+
+		expected_gle = dict(
+			(d[0], d)
+			for d in [
+				["Debtors - _TC", 0, 5000, si.name],
+				["_Test Bank - _TC", 4500, 0, None],
+				["Write Off - _TC", 500.0, 0, None],
+			]
+		)
+
+		self.validate_gl_entries(pe.name, expected_gle)
+
+		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
+		self.assertEqual(outstanding_amount, 0)
+
+	def test_payment_entry_multicurrency_accounting_si_with_early_payment_discount(self):
+		"""
+		1. Multi-currency SI with multi-currency accounting
+		2. PE with early payment discount and also exchange loss
+		3. Test if Paid Amount is calculated in transaction currency
+		4. Test if deductions are calculated in base/company currency
+		5. Test if exchange loss is reflected in difference
+		"""
+		si = create_sales_invoice(
+			customer="_Test Customer USD",
+			debit_to="_Test Receivable USD - _TC",
+			currency="USD",
+			conversion_rate=50,
+			do_not_save=1,
+		)
+		create_payment_terms_template_with_discount()
+		si.payment_terms_template = "Test Discount Template"
+
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+		si.save()
+		si.submit()
+
+		pe = get_payment_entry(
+			"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
+		)
+		pe.reference_no = si.name
+		pe.reference_date = nowdate()
+
+		# Early payment discount loss on income
+		self.assertEqual(pe.paid_amount, 90.0)
+		self.assertEqual(pe.received_amount, 4200.0)  # 5000 - 500 (discount) - 300 (exchange loss)
+		self.assertEqual(pe.deductions[0].amount, 500.0)
+		self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
+
+		# Exchange loss
+		self.assertEqual(pe.difference_amount, 300.0)
+
+		pe.append(
+			"deductions",
+			{
+				"account": "_Test Exchange Gain/Loss - _TC",
+				"cost_center": "_Test Cost Center - _TC",
+				"amount": 300.0,
+			},
+		)
+
+		pe.insert()
+		pe.submit()
+
+		self.assertEqual(pe.difference_amount, 0.0)
+
+		expected_gle = dict(
+			(d[0], d)
+			for d in [
+				["_Test Receivable USD - _TC", 0, 5000, si.name],
+				["_Test Bank - _TC", 4200, 0, None],
+				["Write Off - _TC", 500.0, 0, None],
+				["_Test Exchange Gain/Loss - _TC", 300.0, 0, None],
+			]
+		)
+
+		self.validate_gl_entries(pe.name, expected_gle)
+
+		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
+		self.assertEqual(outstanding_amount, 0)
+
 	def test_payment_against_purchase_invoice_to_check_status(self):
 		pi = make_purchase_invoice(
 			supplier="_Test Supplier USD",
@@ -839,24 +1038,27 @@
 		).insert()
 
 
-def create_payment_terms_template_with_discount():
+def create_payment_terms_template_with_discount(
+	name=None, discount_type=None, discount=None, template_name=None
+):
+	create_payment_term(name or "30 Credit Days with 10% Discount")
+	template_name = template_name or "Test Discount Template"
 
-	create_payment_term("30 Credit Days with 10% Discount")
-
-	if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
-		payment_term_template = frappe.get_doc(
+	if not frappe.db.exists("Payment Terms Template", template_name):
+		frappe.get_doc(
 			{
 				"doctype": "Payment Terms Template",
-				"template_name": "Test Discount Template",
+				"template_name": template_name,
 				"allocate_payment_based_on_payment_terms": 1,
 				"terms": [
 					{
 						"doctype": "Payment Terms Template Detail",
-						"payment_term": "30 Credit Days with 10% Discount",
+						"payment_term": name or "30 Credit Days with 10% Discount",
 						"invoice_portion": 100,
 						"credit_days_based_on": "Day(s) after invoice date",
 						"credit_days": 2,
-						"discount": 10,
+						"discount_type": discount_type or "Percentage",
+						"discount": discount or 10,
 						"discount_validity_based_on": "Day(s) after invoice date",
 						"discount_validity": 1,
 					}
diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
index 61a1462..1c31829 100644
--- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
+++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
@@ -3,6 +3,7 @@
  "creation": "2016-06-15 15:56:30.815503",
  "doctype": "DocType",
  "editable_grid": 1,
+ "engine": "InnoDB",
  "field_order": [
   "account",
   "cost_center",
@@ -17,9 +18,7 @@
    "in_list_view": 1,
    "label": "Account",
    "options": "Account",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "cost_center",
@@ -28,37 +27,30 @@
    "label": "Cost Center",
    "options": "Cost Center",
    "print_hide": 1,
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Amount",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_2",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "description",
    "fieldtype": "Small Text",
-   "label": "Description",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Description"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-12 20:38:08.110674",
+ "modified": "2023-03-06 07:11:57.739619",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry Deduction",
@@ -66,5 +58,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index d986f32..caffac5 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -272,4 +272,32 @@
 	}
 };
 
+frappe.ui.form.on('Payment Reconciliation Allocation', {
+	allocated_amount: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		// filter invoice
+		let invoice = frm.doc.invoices.filter((x) => (x.invoice_number == row.invoice_number));
+		// filter payment
+		let payment = frm.doc.payments.filter((x) => (x.reference_name == row.reference_name));
+
+		frm.call({
+			doc: frm.doc,
+			method: 'calculate_difference_on_allocation_change',
+			args: {
+				payment_entry: payment,
+				invoice: invoice,
+				allocated_amount: row.allocated_amount
+			},
+			callback: (r) => {
+				if (r.message) {
+					row.difference_amount = r.message;
+					frm.refresh();
+				}
+			}
+		});
+	}
+});
+
+
+
 extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index c9e3998..d8082d0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -234,6 +234,15 @@
 		return difference_amount
 
 	@frappe.whitelist()
+	def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
+		invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
+		invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number"))
+		new_difference_amount = self.get_difference_amount(
+			payment_entry[0], invoice[0], allocated_amount
+		)
+		return new_difference_amount
+
+	@frappe.whitelist()
 	def allocate_entries(self, args):
 		self.validate_entries()
 
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 7005c17..11d6d5f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -497,10 +497,16 @@
 	if dt in ["Sales Order", "Purchase Order"]:
 		grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
 	elif dt in ["Sales Invoice", "Purchase Invoice"]:
-		if ref_doc.party_account_currency == ref_doc.currency:
-			grand_total = flt(ref_doc.outstanding_amount)
-		else:
-			grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
+		if not ref_doc.get("is_pos"):
+			if ref_doc.party_account_currency == ref_doc.currency:
+				grand_total = flt(ref_doc.outstanding_amount)
+			else:
+				grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
+		elif dt == "Sales Invoice":
+			for pay in ref_doc.payments:
+				if pay.type == "Phone" and pay.account == payment_account:
+					grand_total = pay.amount
+					break
 	elif dt == "POS Invoice":
 		for pay in ref_doc.payments:
 			if pay.type == "Phone" and pay.account == payment_account:
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 4279aa4..e17a846 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -6,6 +6,7 @@
 import frappe
 
 from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.setup.utils import get_exchange_rate
@@ -74,6 +75,29 @@
 		self.assertEqual(pr.reference_name, si_usd.name)
 		self.assertEqual(pr.currency, "USD")
 
+	def test_payment_entry_against_purchase_invoice(self):
+		si_usd = make_purchase_invoice(
+			customer="_Test Supplier USD",
+			debit_to="_Test Payable USD - _TC",
+			currency="USD",
+			conversion_rate=50,
+		)
+
+		pr = make_payment_request(
+			dt="Purchase Invoice",
+			dn=si_usd.name,
+			recipient_id="user@example.com",
+			mute_email=1,
+			payment_gateway_account="_Test Gateway - USD",
+			submit_doc=1,
+			return_doc=1,
+		)
+
+		pe = pr.create_payment_entry()
+		pr.load_from_db()
+
+		self.assertEqual(pr.status, "Paid")
+
 	def test_payment_entry(self):
 		frappe.db.set_value(
 			"Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e2b4a1a..5c9168b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -82,7 +82,11 @@
 
 		if(doc.docstatus == 1 && doc.outstanding_amount != 0
 			&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
-			this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create'));
+			this.frm.add_custom_button(
+				__('Payment'),
+				() => this.make_payment_entry(),
+				__('Create')
+			);
 			cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 54caf6f..b4d369e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -118,6 +118,7 @@
   "paid_amount",
   "advances_section",
   "allocate_advances_automatically",
+  "only_include_allocated_payments",
   "get_advances",
   "advances",
   "advance_tax",
@@ -1550,17 +1551,24 @@
    "fieldname": "named_place",
    "fieldtype": "Data",
    "label": "Named Place"
+  },
+  {
+   "default": "0",
+   "depends_on": "allocate_advances_automatically",
+   "description": "Advance payments allocated against orders will only be fetched",
+   "fieldname": "only_include_allocated_payments",
+   "fieldtype": "Check",
+   "label": "Only Include Allocated Payments"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-28 19:18:56.586321",
+ "modified": "2023-04-03 22:57:14.074982",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
- "name_case": "Title Case",
  "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b79af71..a617447 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -117,7 +117,7 @@
 		self.validate_expense_account()
 		self.set_against_expense_account()
 		self.validate_write_off_account()
-		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
+		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
 		self.create_remarks()
 		self.set_status()
 		self.validate_purchase_receipt_if_update_stock()
@@ -232,7 +232,7 @@
 		)
 
 		if (
-			cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
+			cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))
 			and not self.is_return
 			and not self.is_internal_supplier
 		):
@@ -581,6 +581,7 @@
 
 		self.make_supplier_gl_entry(gl_entries)
 		self.make_item_gl_entries(gl_entries)
+		self.make_precision_loss_gl_entry(gl_entries)
 
 		if self.check_asset_cwip_enabled():
 			self.get_asset_gl_entry(gl_entries)
@@ -975,6 +976,28 @@
 							item.item_tax_amount, item.precision("item_tax_amount")
 						)
 
+	def make_precision_loss_gl_entry(self, gl_entries):
+		round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+			self.company, "Purchase Invoice", self.name
+		)
+
+		precision_loss = self.get("base_net_total") - flt(
+			self.get("net_total") * self.conversion_rate, self.precision("net_total")
+		)
+
+		if precision_loss:
+			gl_entries.append(
+				self.get_gl_dict(
+					{
+						"account": round_off_account,
+						"against": self.supplier,
+						"credit": precision_loss,
+						"cost_center": self.cost_center or round_off_cost_center,
+						"remarks": _("Net total calculation precision loss"),
+					}
+				)
+			)
+
 	def get_asset_gl_entry(self, gl_entries):
 		arbnb_account = self.get_company_default("asset_received_but_not_billed")
 		eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 47e3f9b..56e412b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -93,9 +93,12 @@
 
 		if (doc.docstatus == 1 && doc.outstanding_amount!=0
 			&& !(cint(doc.is_return) && doc.return_against)) {
-			cur_frm.add_custom_button(__('Payment'),
-				this.make_payment_entry, __('Create'));
-			cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
+			this.frm.add_custom_button(
+				__('Payment'),
+				() => this.make_payment_entry(),
+				__('Create')
+			);
+			this.frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
 		if(doc.docstatus==1 && !doc.is_return) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 2a8ff40..a41e13c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -120,6 +120,7 @@
   "account_for_change_amount",
   "advances_section",
   "allocate_advances_automatically",
+  "only_include_allocated_payments",
   "get_advances",
   "advances",
   "write_off_section",
@@ -2126,6 +2127,14 @@
    "fieldname": "named_place",
    "fieldtype": "Data",
    "label": "Named Place"
+  },
+  {
+   "default": "0",
+   "depends_on": "allocate_advances_automatically",
+   "description": "Advance payments allocated against orders will only be fetched",
+   "fieldname": "only_include_allocated_payments",
+   "fieldtype": "Check",
+   "label": "Only Include Allocated Payments"
   }
  ],
  "icon": "fa fa-file-text",
@@ -2138,7 +2147,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2023-03-13 11:43:15.883055",
+ "modified": "2023-04-03 22:55:14.206473",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 5cda276..db61995 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -145,7 +145,7 @@
 
 		self.set_against_income_account()
 		self.validate_time_sheets_are_submitted()
-		self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
+		self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
 		if not self.is_return:
 			self.validate_serial_numbers()
 		else:
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index b217f00..ac9368e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -174,6 +174,9 @@
 			party_type, party.name, "tax_withholding_category"
 		)
 
+	if not party_details.get("tax_category") and pos_profile:
+		party_details["tax_category"] = frappe.get_value("POS Profile", pos_profile, "tax_category")
+
 	return party_details
 
 
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 57d8049..f21c94b 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -25,6 +25,7 @@
 		["posting_date", "<=", filters.get("to_date")],
 		["against_voucher_type", "=", "Asset"],
 		["account", "in", depreciation_accounts],
+		["is_cancelled", "=", 0],
 	]
 
 	if filters.get("asset"):
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index 449ebdc..306af72 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -4,6 +4,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import getdate, nowdate
 
 
@@ -91,4 +92,65 @@
 		as_list=1,
 	)
 
-	return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
+	# Loan Disbursement
+	loan_disbursement = frappe.qb.DocType("Loan Disbursement")
+
+	query = (
+		frappe.qb.from_(loan_disbursement)
+		.select(
+			ConstantColumn("Loan Disbursement").as_("payment_document_type"),
+			loan_disbursement.name.as_("payment_entry"),
+			loan_disbursement.disbursement_date.as_("posting_date"),
+			loan_disbursement.reference_number.as_("cheque_no"),
+			loan_disbursement.clearance_date.as_("clearance_date"),
+			loan_disbursement.applicant.as_("against"),
+			-loan_disbursement.disbursed_amount.as_("amount"),
+		)
+		.where(loan_disbursement.docstatus == 1)
+		.where(loan_disbursement.disbursement_date >= filters["from_date"])
+		.where(loan_disbursement.disbursement_date <= filters["to_date"])
+		.where(loan_disbursement.disbursement_account == filters["account"])
+		.orderby(loan_disbursement.disbursement_date, order=frappe.qb.desc)
+		.orderby(loan_disbursement.name, order=frappe.qb.desc)
+	)
+
+	if filters.get("from_date"):
+		query = query.where(loan_disbursement.disbursement_date >= filters["from_date"])
+	if filters.get("to_date"):
+		query = query.where(loan_disbursement.disbursement_date <= filters["to_date"])
+
+	loan_disbursements = query.run(as_list=1)
+
+	# Loan Repayment
+	loan_repayment = frappe.qb.DocType("Loan Repayment")
+
+	query = (
+		frappe.qb.from_(loan_repayment)
+		.select(
+			ConstantColumn("Loan Repayment").as_("payment_document_type"),
+			loan_repayment.name.as_("payment_entry"),
+			loan_repayment.posting_date.as_("posting_date"),
+			loan_repayment.reference_number.as_("cheque_no"),
+			loan_repayment.clearance_date.as_("clearance_date"),
+			loan_repayment.applicant.as_("against"),
+			loan_repayment.amount_paid.as_("amount"),
+		)
+		.where(loan_repayment.docstatus == 1)
+		.where(loan_repayment.posting_date >= filters["from_date"])
+		.where(loan_repayment.posting_date <= filters["to_date"])
+		.where(loan_repayment.payment_account == filters["account"])
+		.orderby(loan_repayment.posting_date, order=frappe.qb.desc)
+		.orderby(loan_repayment.name, order=frappe.qb.desc)
+	)
+
+	if filters.get("from_date"):
+		query = query.where(loan_repayment.posting_date >= filters["from_date"])
+	if filters.get("to_date"):
+		query = query.where(loan_repayment.posting_date <= filters["to_date"])
+
+	loan_repayments = query.run(as_list=1)
+
+	return sorted(
+		journal_entries + payment_entries + loan_disbursements + loan_repayments,
+		key=lambda k: k[2] or getdate(nowdate()),
+	)
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 010284c..2100f26 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -58,9 +58,8 @@
 		{
 			"fieldname":"party_type",
 			"label": __("Party Type"),
-			"fieldtype": "Link",
-			"options": "Party Type",
-			"default": "",
+			"fieldtype": "Autocomplete",
+			options: Object.keys(frappe.boot.party_account_types),
 			on_change: function() {
 				frappe.query_report.set_filter_value('party', "");
 			}
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 92906c1..2ab9ef6 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -467,14 +467,6 @@
 			else:
 				update_reference_in_payment_entry(entry, doc, do_not_save=True)
 
-		if doc.doctype == "Journal Entry":
-			try:
-				doc.validate_total_debit_and_credit()
-			except Exception as validation_exception:
-				raise frappe.ValidationError(
-					_("Validation Error for {0}").format(doc.name)
-				) from validation_exception
-
 		doc.save(ignore_permissions=True)
 		# re-submit advance entry
 		doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 4951385..b9f16a7 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -466,6 +466,9 @@
 		} else {
 			frm.set_value('purchase_date', purchase_doc.posting_date);
 		}
+		if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
+			frm.set_value('available_for_use_date', frm.doc.purchase_date);
+		}
 		const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
 		if (!item) {
 			doctype_field = frappe.scrub(doctype)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index ea575fd..a1e8f33 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -79,6 +79,9 @@
    "options": "ACC-ASS-.YYYY.-"
   },
   {
+   "depends_on": "item_code",
+   "fetch_from": "item_code.item_name",
+   "fetch_if_empty": 1,
    "fieldname": "asset_name",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -517,7 +520,7 @@
    "table_fieldname": "accounts"
   }
  ],
- "modified": "2023-02-02 00:03:11.706427",
+ "modified": "2023-03-30 15:07:41.542374",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index b5b7ba8..6001254 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -8,16 +8,12 @@
 import frappe
 from frappe import _
 from frappe.utils import (
-	add_months,
 	cint,
-	date_diff,
 	flt,
 	get_datetime,
 	get_last_day,
 	get_link_to_form,
 	getdate,
-	is_last_day_of_the_month,
-	month_diff,
 	nowdate,
 	today,
 )
@@ -239,30 +235,6 @@
 				self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
 			)
 
-	# if it returns True, depreciation_amount will not be equal for the first and last rows
-	def check_is_pro_rata(self, row):
-		has_pro_rata = False
-
-		# if not existing asset, from_date = available_for_use_date
-		# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
-		# from_date = 01/01/2022
-		from_date = self.get_modified_available_for_use_date(row)
-		days = date_diff(row.depreciation_start_date, from_date) + 1
-
-		# if frequency_of_depreciation is 12 months, total_days = 365
-		total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
-
-		if days < total_days:
-			has_pro_rata = True
-
-		return has_pro_rata
-
-	def get_modified_available_for_use_date(self, row):
-		return add_months(
-			self.available_for_use_date,
-			(self.number_of_depreciations_booked * row.frequency_of_depreciation),
-		)
-
 	def validate_asset_finance_books(self, row):
 		if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
 			frappe.throw(
@@ -471,29 +443,6 @@
 
 		return records
 
-	@erpnext.allow_regional
-	def get_depreciation_amount(self, depreciable_value, fb_row):
-		if fb_row.depreciation_method in ("Straight Line", "Manual"):
-			# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
-			if self.flags.increase_in_asset_life:
-				depreciation_amount = (
-					flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
-				) / (date_diff(self.to_date, self.available_for_use_date) / 365)
-			# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
-			elif self.flags.increase_in_asset_value_due_to_repair:
-				depreciation_amount = (
-					flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
-				) / flt(fb_row.total_number_of_depreciations)
-			# if the Depreciation Schedule is being prepared for the first time
-			else:
-				depreciation_amount = (
-					flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
-				) / flt(fb_row.total_number_of_depreciations)
-		else:
-			depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
-
-		return depreciation_amount
-
 	def validate_make_gl_entry(self):
 		purchase_document = self.get_purchase_document()
 		if not purchase_document:
@@ -618,7 +567,12 @@
 		float_precision = cint(frappe.db.get_default("float_precision")) or 2
 
 		if args.get("depreciation_method") == "Double Declining Balance":
-			return 200.0 / args.get("total_number_of_depreciations")
+			return 200.0 / (
+				(
+					flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation"))
+				)
+				/ 12
+			)
 
 		if args.get("depreciation_method") == "Written Down Value":
 			if (
@@ -635,17 +589,20 @@
 			else:
 				value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
 
-			depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
+			depreciation_rate = math.pow(
+				value,
+				1.0
+				/ (
+					(
+						flt(args.get("total_number_of_depreciations"), 2)
+						* flt(args.get("frequency_of_depreciation"))
+					)
+					/ 12
+				),
+			)
 
 			return flt((100 * (1 - depreciation_rate)), float_precision)
 
-	def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
-		days = date_diff(to_date, from_date)
-		months = month_diff(to_date, from_date)
-		total_days = get_total_days(to_date, row.frequency_of_depreciation)
-
-		return (depreciation_amount * flt(days)) / flt(total_days), days, months
-
 
 def update_maintenance_status():
 	assets = frappe.get_all(
@@ -889,15 +846,6 @@
 	return asset.get_value_after_depreciation(finance_book)
 
 
-def get_total_days(date, frequency):
-	period_start_date = add_months(date, cint(frequency) * -1)
-
-	if is_last_day_of_the_month(date):
-		period_start_date = get_last_day(period_start_date)
-
-	return date_diff(date, period_start_date)
-
-
 @frappe.whitelist()
 def split_asset(asset_name, split_qty):
 	asset = frappe.get_doc("Asset", asset_name)
diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js
index 3d00eb7..5f53b00 100644
--- a/erpnext/assets/doctype/asset/asset_list.js
+++ b/erpnext/assets/doctype/asset/asset_list.js
@@ -36,7 +36,7 @@
 		}
 	},
 	onload: function(me) {
-		me.page.add_action_item('Make Asset Movement', function() {
+		me.page.add_action_item(__("Make Asset Movement"), function() {
 			const assets = me.get_checked_items();
 			frappe.call({
 				method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index fb6e174..028e3d6 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -249,10 +249,16 @@
 	asset_links = get_comma_separated_asset_links(failed_asset_names)
 
 	message = (
-		_("Hi,")
-		+ "<br>"
-		+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+		_("Hello,")
+		+ "<br><br>"
+		+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
+			asset_links
+		)
 		+ "."
+		+ "<br><br>"
+		+ _(
+			"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+		)
 	)
 
 	frappe.sendmail(recipients=recipients, subject=subject, message=message)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 2c9772d..cde0280 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -29,8 +29,11 @@
 	scrap_asset,
 )
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+	_check_is_pro_rata,
+	_get_pro_rata_amt,
 	get_asset_depr_schedule_doc,
 	get_depr_schedule,
+	get_depreciation_amount,
 )
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
 	make_purchase_invoice as make_invoice,
@@ -234,7 +237,7 @@
 			asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
 			asset.precision("gross_purchase_amount"),
 		)
-		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+		pro_rata_amount, _, _ = _get_pro_rata_amt(
 			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
@@ -321,7 +324,7 @@
 		self.assertEquals(second_asset_depr_schedule.status, "Active")
 		self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
 
-		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+		pro_rata_amount, _, _ = _get_pro_rata_amt(
 			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
@@ -857,12 +860,12 @@
 		)
 
 		expected_schedules = [
-			["2022-02-28", 647.25, 647.25],
-			["2022-03-31", 1210.71, 1857.96],
-			["2022-04-30", 1053.99, 2911.95],
-			["2022-05-31", 917.55, 3829.5],
-			["2022-06-30", 798.77, 4628.27],
-			["2022-07-15", 371.73, 5000.0],
+			["2022-02-28", 310.89, 310.89],
+			["2022-03-31", 654.45, 965.34],
+			["2022-04-30", 654.45, 1619.79],
+			["2022-05-31", 654.45, 2274.24],
+			["2022-06-30", 654.45, 2928.69],
+			["2022-07-15", 2071.31, 5000.0],
 		]
 
 		schedules = [
@@ -938,7 +941,7 @@
 			},
 		)
 
-		depreciation_amount = asset.get_depreciation_amount(100000, asset.finance_books[0])
+		depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
 		self.assertEqual(depreciation_amount, 30000)
 
 	def test_make_depr_schedule(self):
@@ -997,7 +1000,7 @@
 			},
 		)
 
-		has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+		has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0])
 		self.assertFalse(has_pro_rata)
 
 		asset.finance_books = []
@@ -1012,7 +1015,7 @@
 			},
 		)
 
-		has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+		has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0])
 		self.assertTrue(has_pro_rata)
 
 	def test_expected_value_after_useful_life_greater_than_purchase_amount(self):
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index d23edfa..116593a 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -8,12 +8,16 @@
 	add_days,
 	add_months,
 	cint,
+	date_diff,
 	flt,
 	get_last_day,
 	getdate,
 	is_last_day_of_the_month,
+	month_diff,
 )
 
+import erpnext
+
 
 class AssetDepreciationSchedule(Document):
 	def before_save(self):
@@ -185,7 +189,7 @@
 	):
 		asset_doc.validate_asset_finance_books(row)
 
-		value_after_depreciation = self._get_value_after_depreciation_for_making_schedule(asset_doc, row)
+		value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
 		row.value_after_depreciation = value_after_depreciation
 
 		if update_asset_finance_book_row:
@@ -195,21 +199,46 @@
 			self.number_of_depreciations_booked
 		)
 
-		has_pro_rata = asset_doc.check_is_pro_rata(row)
+		has_pro_rata = _check_is_pro_rata(asset_doc, row)
 		if has_pro_rata:
 			number_of_pending_depreciations += 1
 
+		has_wdv_or_dd_non_yearly_pro_rata = False
+		if (
+			row.depreciation_method in ("Written Down Value", "Double Declining Balance")
+			and cint(row.frequency_of_depreciation) != 12
+		):
+			has_wdv_or_dd_non_yearly_pro_rata = _check_is_pro_rata(
+				asset_doc, row, wdv_or_dd_non_yearly=True
+			)
+
 		skip_row = False
 		should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
 
+		depreciation_amount = 0
+
 		for n in range(start, number_of_pending_depreciations):
 			# If depreciation is already completed (for double declining balance)
 			if skip_row:
 				continue
 
-			depreciation_amount = asset_doc.get_depreciation_amount(value_after_depreciation, row)
+			if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
+				prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
+			else:
+				prev_depreciation_amount = 0
 
-			if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
+			depreciation_amount = get_depreciation_amount(
+				asset_doc,
+				value_after_depreciation,
+				row,
+				n,
+				prev_depreciation_amount,
+				has_wdv_or_dd_non_yearly_pro_rata,
+			)
+
+			if not has_pro_rata or (
+				n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
+			):
 				schedule_date = add_months(
 					row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
 				)
@@ -227,8 +256,11 @@
 				if self.depreciation_schedule:
 					from_date = self.depreciation_schedule[-1].schedule_date
 
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, from_date, date_of_disposal
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					from_date,
+					date_of_disposal,
 				)
 
 				if depreciation_amount > 0:
@@ -240,12 +272,20 @@
 				break
 
 			# For first row
-			if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
+			if (
+				(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
+				and not self.opening_accumulated_depreciation
+				and n == 0
+			):
 				from_date = add_days(
 					asset_doc.available_for_use_date, -1
 				)  # needed to calc depr amount for available_for_use_date too
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, from_date, row.depreciation_start_date
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					from_date,
+					row.depreciation_start_date,
+					has_wdv_or_dd_non_yearly_pro_rata,
 				)
 
 				# For first depr schedule date will be the start date
@@ -264,8 +304,12 @@
 
 				depreciation_amount_without_pro_rata = depreciation_amount
 
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, schedule_date, asset_doc.to_date
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					schedule_date,
+					asset_doc.to_date,
+					has_wdv_or_dd_non_yearly_pro_rata,
 				)
 
 				depreciation_amount = self.get_adjusted_depreciation_amount(
@@ -373,15 +417,142 @@
 				accumulated_depreciation, d.precision("accumulated_depreciation_amount")
 			)
 
-	def _get_value_after_depreciation_for_making_schedule(self, asset_doc, fb_row):
-		if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
-			value_after_depreciation = flt(fb_row.value_after_depreciation)
-		else:
-			value_after_depreciation = flt(self.gross_purchase_amount) - flt(
-				self.opening_accumulated_depreciation
-			)
 
-		return value_after_depreciation
+def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
+	if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
+		value_after_depreciation = flt(fb_row.value_after_depreciation)
+	else:
+		value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
+			asset_doc.opening_accumulated_depreciation
+		)
+
+	return value_after_depreciation
+
+
+# if it returns True, depreciation_amount will not be equal for the first and last rows
+def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
+	has_pro_rata = False
+
+	# if not existing asset, from_date = available_for_use_date
+	# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
+	# from_date = 01/01/2022
+	from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly)
+	days = date_diff(row.depreciation_start_date, from_date) + 1
+
+	if wdv_or_dd_non_yearly:
+		total_days = get_total_days(row.depreciation_start_date, 12)
+	else:
+		# if frequency_of_depreciation is 12 months, total_days = 365
+		total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
+
+	if days < total_days:
+		has_pro_rata = True
+
+	return has_pro_rata
+
+
+def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False):
+	if wdv_or_dd_non_yearly:
+		return add_months(
+			asset_doc.available_for_use_date,
+			(asset_doc.number_of_depreciations_booked * 12),
+		)
+	else:
+		return add_months(
+			asset_doc.available_for_use_date,
+			(asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
+		)
+
+
+def _get_pro_rata_amt(
+	row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False
+):
+	days = date_diff(to_date, from_date)
+	months = month_diff(to_date, from_date)
+	if has_wdv_or_dd_non_yearly_pro_rata:
+		total_days = get_total_days(to_date, 12)
+	else:
+		total_days = get_total_days(to_date, row.frequency_of_depreciation)
+
+	return (depreciation_amount * flt(days)) / flt(total_days), days, months
+
+
+def get_total_days(date, frequency):
+	period_start_date = add_months(date, cint(frequency) * -1)
+
+	if is_last_day_of_the_month(date):
+		period_start_date = get_last_day(period_start_date)
+
+	return date_diff(date, period_start_date)
+
+
+@erpnext.allow_regional
+def get_depreciation_amount(
+	asset,
+	depreciable_value,
+	row,
+	schedule_idx=0,
+	prev_depreciation_amount=0,
+	has_wdv_or_dd_non_yearly_pro_rata=False,
+):
+	if row.depreciation_method in ("Straight Line", "Manual"):
+		return get_straight_line_or_manual_depr_amount(asset, row)
+	else:
+		return get_wdv_or_dd_depr_amount(
+			depreciable_value,
+			row.rate_of_depreciation,
+			row.frequency_of_depreciation,
+			schedule_idx,
+			prev_depreciation_amount,
+			has_wdv_or_dd_non_yearly_pro_rata,
+		)
+
+
+def get_straight_line_or_manual_depr_amount(asset, row):
+	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
+	if asset.flags.increase_in_asset_life:
+		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
+			date_diff(asset.to_date, asset.available_for_use_date) / 365
+		)
+	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
+	elif asset.flags.increase_in_asset_value_due_to_repair:
+		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
+			row.total_number_of_depreciations
+		)
+	# if the Depreciation Schedule is being prepared for the first time
+	else:
+		return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
+			row.total_number_of_depreciations
+		)
+
+
+def get_wdv_or_dd_depr_amount(
+	depreciable_value,
+	rate_of_depreciation,
+	frequency_of_depreciation,
+	schedule_idx,
+	prev_depreciation_amount,
+	has_wdv_or_dd_non_yearly_pro_rata,
+):
+	if cint(frequency_of_depreciation) == 12:
+		return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
+	else:
+		if has_wdv_or_dd_non_yearly_pro_rata:
+			if schedule_idx == 0:
+				return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
+			elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
+				return (
+					flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
+				)
+			else:
+				return prev_depreciation_amount
+		else:
+			if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
+				return (
+					flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
+				)
+			else:
+				return prev_depreciation_amount
 
 
 def make_draft_asset_depr_schedules_if_not_present(asset_doc):
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 0028d84..8303141 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -84,6 +84,8 @@
 		next_due_date = add_years(start_date, 1)
 	if periodicity == "2 Yearly":
 		next_due_date = add_years(start_date, 2)
+	if periodicity == "3 Yearly":
+		next_due_date = add_years(start_date, 3)
 	if periodicity == "Quarterly":
 		next_due_date = add_months(start_date, 3)
 	if end_date and (
diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
index 20963e3..b7cb23e 100644
--- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
+++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
@@ -1,664 +1,156 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-10-20 07:10:55.903571", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2017-10-20 07:10:55.903571",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "maintenance_task",
+  "maintenance_type",
+  "column_break_2",
+  "maintenance_status",
+  "section_break_2",
+  "start_date",
+  "periodicity",
+  "column_break_4",
+  "end_date",
+  "certificate_required",
+  "section_break_9",
+  "assign_to",
+  "column_break_10",
+  "assign_to_name",
+  "section_break_10",
+  "next_due_date",
+  "column_break_14",
+  "last_completion_date",
+  "section_break_7",
+  "description"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "maintenance_task", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 1, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Maintenance Task", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_task",
+   "fieldtype": "Data",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Maintenance Task",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "maintenance_type", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Maintenance Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Preventive Maintenance\nCalibration", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_type",
+   "fieldtype": "Select",
+   "label": "Maintenance Type",
+   "options": "Preventive Maintenance\nCalibration"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_2", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "", 
-   "fieldname": "maintenance_status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Maintenance Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Planned\nOverdue\nCancelled", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Maintenance Status",
+   "options": "Planned\nOverdue\nCancelled",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_2", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Today", 
-   "fieldname": "start_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Start Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "Today",
+   "fieldname": "start_date",
+   "fieldtype": "Date",
+   "label": "Start Date",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "periodicity", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Periodicity", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "periodicity",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Periodicity",
+   "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_4", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "end_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "End Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "end_date",
+   "fieldtype": "Date",
+   "label": "End Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "certificate_required", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Certificate Required", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 1, 
-   "set_only_once": 1, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "certificate_required",
+   "fieldtype": "Check",
+   "label": "Certificate Required",
+   "search_index": 1,
+   "set_only_once": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_9", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_9",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "assign_to", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Assign To", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "User", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "assign_to",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Assign To",
+   "options": "User"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_10", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_10",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fetch_from": "assign_to.full_name",
-   "fieldname": "assign_to_name", 
-   "fieldtype": "Read Only", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Assign to Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "assign_to_name",
+   "fieldtype": "Read Only",
+   "label": "Assign to Name"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_10", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_10",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "next_due_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Next Due Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "next_due_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Next Due Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_14", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "last_completion_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Last Completion Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "last_completion_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Last Completion Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_7", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_7",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-06-18 16:12:04.330021", 
- "modified_by": "Administrator", 
- "module": "Assets", 
- "name": "Asset Maintenance Task", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2023-03-23 07:03:07.113452",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance Task",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
index ae0e1bd..d07f40c 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
@@ -49,7 +49,7 @@
 			frm.call({
 				method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
 				args: {
-					asset: frm.doc.asset,
+					asset_name: frm.doc.asset,
 					finance_book: frm.doc.finance_book
 				},
 				callback: function(r) {
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 31d6ffa..0213328 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -14,6 +14,7 @@
 from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
 	get_asset_depr_schedule_doc,
+	get_depreciation_amount,
 )
 
 
@@ -162,7 +163,7 @@
 						depreciation_amount = days * rate_per_day
 						from_date = data.schedule_date
 					else:
-						depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, d)
+						depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
 
 					if depreciation_amount:
 						value_after_depreciation -= flt(depreciation_amount)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 47089f7..c6c9f1f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -236,7 +236,11 @@
 							this.make_purchase_invoice, __('Create'));
 
 					if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
-						cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
+						this.frm.add_custom_button(
+							__('Payment'),
+							() => this.make_payment_entry(),
+							__('Create')
+						);
 					}
 
 					if(flt(doc.per_billed) < 100) {
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index f0899b0..1ae6f03 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -64,7 +64,7 @@
 			// custom buttons
 			frm.add_custom_button(__('Accounting Ledger'), function () {
 				frappe.set_route('query-report', 'General Ledger',
-					{ party_type: 'Supplier', party: frm.doc.name });
+					{ party_type: 'Supplier', party: frm.doc.name, party_name: frm.doc.supplier_name });
 			}, __("View"));
 
 			frm.add_custom_button(__('Accounts Payable'), function () {
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index bab6bb7..f65627c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -515,6 +515,8 @@
 				parent_dict.update({"customer": parent_dict.get("party_name")})
 
 			self.pricing_rules = []
+			basic_item_details_map = {}
+
 			for item in self.get("items"):
 				if item.get("item_code"):
 					args = parent_dict.copy()
@@ -533,7 +535,17 @@
 					if self.get("is_subcontracted"):
 						args["is_subcontracted"] = self.is_subcontracted
 
-					ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
+					basic_details = basic_item_details_map.get(item.item_code)
+					ret, basic_item_details = get_item_details(
+						args,
+						self,
+						for_validate=True,
+						overwrite_warehouse=False,
+						return_basic_details=True,
+						basic_details=basic_details,
+					)
+
+					basic_item_details_map.setdefault(item.item_code, basic_item_details)
 
 					for fieldname, value in ret.items():
 						if item.meta.get_field(fieldname) and value is not None:
@@ -833,7 +845,9 @@
 	def set_advances(self):
 		"""Returns list of advances against Account, Party, Reference"""
 
-		res = self.get_advance_entries()
+		res = self.get_advance_entries(
+			include_unallocated=not cint(self.get("only_include_allocated_payments"))
+		)
 
 		self.set("advances", [])
 		advance_allocated = 0
@@ -1232,7 +1246,7 @@
 				)
 			)
 
-	def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
+	def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on):
 		from erpnext.controllers.status_updater import get_allowance_for
 
 		item_allowance = {}
@@ -1245,17 +1259,20 @@
 
 		total_overbilled_amt = 0.0
 
+		reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)]
+		reference_details = self.get_billing_reference_details(
+			reference_names, ref_dt + " Item", based_on
+		)
+
 		for item in self.get("items"):
 			if not item.get(item_ref_dn):
 				continue
 
-			ref_amt = flt(
-				frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on),
-				self.precision(based_on, item),
-			)
+			ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
+
 			if not ref_amt:
 				frappe.msgprint(
-					_("System will not check overbilling since amount for Item {0} in {1} is zero").format(
+					_("System will not check over billing since amount for Item {0} in {1} is zero").format(
 						item.item_code, ref_dt
 					),
 					title=_("Warning"),
@@ -1302,6 +1319,16 @@
 				alert=True,
 			)
 
+	def get_billing_reference_details(self, reference_names, reference_doctype, based_on):
+		return frappe._dict(
+			frappe.get_all(
+				reference_doctype,
+				filters={"name": ("in", reference_names)},
+				fields=["name", based_on],
+				as_list=1,
+			)
+		)
+
 	def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
 		"""
 		Returns Sum of Amount of
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index dd2a670..58cab14 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -464,7 +464,7 @@
 					ifnull((select
 						ifnull(sum(case when abs(%(target_ref_field)s) > abs(%(target_field)s) then abs(%(target_field)s) else abs(%(target_ref_field)s) end), 0)
 						/ sum(abs(%(target_ref_field)s)) * 100
-					from `tab%(target_dt)s` where parent='%(name)s' having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
+					from `tab%(target_dt)s` where parent='%(name)s' and parenttype='%(target_parent_dt)s' having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
 					%(update_modified)s
 				where name='%(name)s'"""
 				% args
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index cc80f6c..0575429 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -455,7 +455,7 @@
 					"allow_zero_valuation": 1,
 				}
 			)
-			rm_obj.rate = get_incoming_rate(args)
+			rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
 
 		if self.doctype == self.subcontract_data.order_doctype:
 			rm_obj.required_qty = qty
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index f4b6e91..6a5fead 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -33,7 +33,6 @@
 	def after_insert(self):
 		if self.opportunity_from == "Lead":
 			frappe.get_doc("Lead", self.party_name).set_status(update=True)
-			self.disable_lead()
 
 			link_open_tasks(self.opportunity_from, self.party_name, self)
 			link_open_events(self.opportunity_from, self.party_name, self)
@@ -119,10 +118,6 @@
 				prospect.flags.ignore_mandatory = True
 				prospect.save()
 
-	def disable_lead(self):
-		if self.opportunity_from == "Lead":
-			frappe.db.set_value("Lead", self.party_name, {"disabled": 1, "docstatus": 1})
-
 	def make_new_lead_if_required(self):
 		"""Set lead against new opportunity"""
 		if (not self.get("party_name")) and self.contact_email:
diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py
index 8660c73..7b8c43b 100644
--- a/erpnext/crm/report/lead_details/lead_details.py
+++ b/erpnext/crm/report/lead_details/lead_details.py
@@ -98,7 +98,7 @@
 			`tabAddress`.name=`tabDynamic Link`.parent)
 		WHERE
 			company = %(company)s
-			AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
+			AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
 			{conditions}
 		ORDER BY
 			`tabLead`.creation asc """.format(
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
index 254511c..b37cfa4 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
@@ -82,7 +82,7 @@
 			{join}
 		WHERE
 			`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
-			AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
+			AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s
 			{conditions}
 		GROUP BY
 			`tabOpportunity`.name
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
index e41c9da..43b2f67 100644
--- a/erpnext/e_commerce/doctype/website_item/test_website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -199,8 +199,14 @@
 
 		breadcrumbs = get_parent_item_groups(item.item_group)
 
+		settings = frappe.get_cached_doc("E Commerce Settings")
+		if settings.enable_field_filters:
+			base_breadcrumb = "Shop by Category"
+		else:
+			base_breadcrumb = "All Products"
+
 		self.assertEqual(breadcrumbs[0]["name"], "Home")
-		self.assertEqual(breadcrumbs[1]["name"], "All Products")
+		self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb)
 		self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B")  # parent item group
 		self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
 
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index f3aa6a3..e57a30a 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -220,7 +220,7 @@
 		if e.code == "ITEM_LOGIN_REQUIRED":
 			msg = _("There was an error syncing transactions.") + " "
 			msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
-			frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
+			frappe.log_error(message=msg, title=_("Plaid Link Refresh Required"))
 
 	return transactions
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 0cb35c1..862a546 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -28,6 +28,10 @@
 
 override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
 
+override_whitelisted_methods = {
+	"frappe.www.contact.send_message": "erpnext.templates.utils.send_message"
+}
+
 welcome_email = "erpnext.setup.utils.welcome_email"
 
 # setup wizard
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index db699b9..d024022 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -9,15 +9,14 @@
   "production_item_tab",
   "item",
   "company",
-  "item_name",
   "uom",
+  "quantity",
   "cb0",
   "is_active",
   "is_default",
   "allow_alternative_item",
   "set_rate_of_sub_assembly_item_based_on_bom",
   "project",
-  "quantity",
   "image",
   "currency_detail",
   "rm_cost_as_per",
@@ -27,6 +26,8 @@
   "column_break_ivyw",
   "currency",
   "conversion_rate",
+  "materials_section",
+  "items",
   "section_break_21",
   "operations_section_section",
   "with_operations",
@@ -38,8 +39,6 @@
   "operating_cost_per_bom_quantity",
   "operations_section",
   "operations",
-  "materials_section",
-  "items",
   "scrap_section",
   "scrap_items_section",
   "scrap_items",
@@ -59,6 +58,7 @@
   "total_cost",
   "base_total_cost",
   "more_info_tab",
+  "item_name",
   "description",
   "column_break_27",
   "has_variants",
@@ -192,6 +192,7 @@
    "options": "Quality Inspection Template"
   },
   {
+   "collapsible": 1,
    "fieldname": "currency_detail",
    "fieldtype": "Section Break",
    "label": "Cost Configuration"
@@ -417,7 +418,7 @@
   {
    "collapsible": 1,
    "fieldname": "website_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Website"
   },
   {
@@ -482,7 +483,7 @@
   {
    "fieldname": "section_break_21",
    "fieldtype": "Tab Break",
-   "label": "Operations & Materials"
+   "label": "Operations"
   },
   {
    "fieldname": "column_break_23",
@@ -605,7 +606,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-02-13 17:31:37.504565",
+ "modified": "2023-04-06 12:47:58.514795",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 619a415..a085af8 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -943,7 +943,8 @@
 	2) If no value, get last valuation rate from SLE
 	3) If no value, get valuation rate from Item
 	"""
-	from frappe.query_builder.functions import Sum
+	from frappe.query_builder.functions import Count, IfNull, Sum
+	from pypika import Case
 
 	item_code, company = data.get("item_code"), data.get("company")
 	valuation_rate = 0.0
@@ -954,7 +955,14 @@
 		frappe.qb.from_(bin_table)
 		.join(wh_table)
 		.on(bin_table.warehouse == wh_table.name)
-		.select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate"))
+		.select(
+			Case()
+			.when(
+				Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0)
+			)
+			.else_(None)
+			.as_("valuation_rate")
+		)
 		.where((bin_table.item_code == item_code) & (wh_table.company == company))
 	).run(as_dict=True)[0]
 
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
index 51f7b24..7477f95 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -164,7 +164,7 @@
 
 	while current_boms_list:
 		batch_no += 1
-		batch_size = 20_000
+		batch_size = 7_000
 		boms_to_process = current_boms_list[:batch_size]  # slice out batch of 20k BOMs
 
 		# update list to exclude 20K (queued) BOMs
@@ -212,7 +212,7 @@
 			["name", "boms_updated", "status"],
 		)
 		incomplete_level = any(row.get("status") == "Pending" for row in bom_batches)
-		if not bom_batches or not incomplete_level:
+		if not bom_batches or incomplete_level:
 			continue
 
 		# Prep parent BOMs & updated processed BOMs for next level
@@ -252,9 +252,6 @@
 	current_boms = []
 
 	for row in bom_batches:
-		if not row.boms_updated:
-			continue
-
 		boms_updated = json.loads(row.boms_updated)
 		current_boms.extend(boms_updated)
 		boms_updated_dict = {bom: True for bom in boms_updated}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 2624daa..fdaa4a2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -344,6 +344,7 @@
   {
    "fieldname": "prod_plan_references",
    "fieldtype": "Table",
+   "hidden": 1,
    "label": "Production Plan Item Reference",
    "options": "Production Plan Item Reference"
   },
@@ -397,7 +398,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-11-26 14:51:08.774372",
+ "modified": "2023-03-31 10:30:48.118932",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
index 84dee4a..15ef207 100644
--- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
@@ -28,7 +28,7 @@
    "fieldname": "qty",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "qty"
+   "label": "Qty"
   },
   {
    "fieldname": "item_reference",
@@ -40,7 +40,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-07 17:03:49.707487",
+ "modified": "2023-03-31 10:30:14.604051",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Item Reference",
@@ -48,5 +48,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 25e16d6..aa90498 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -22,17 +22,13 @@
   "produced_qty",
   "process_loss_qty",
   "project",
-  "serial_no_and_batch_for_finished_good_section",
-  "has_serial_no",
-  "has_batch_no",
-  "column_break_17",
-  "serial_no",
-  "batch_size",
+  "section_break_ndpq",
+  "required_items",
   "work_order_configuration",
   "settings_section",
   "allow_alternative_item",
   "use_multi_level_bom",
-  "column_break_18",
+  "column_break_17",
   "skip_transfer",
   "from_wip_warehouse",
   "update_consumed_material_cost_in_project",
@@ -42,9 +38,14 @@
   "column_break_12",
   "fg_warehouse",
   "scrap_warehouse",
+  "serial_no_and_batch_for_finished_good_section",
+  "has_serial_no",
+  "has_batch_no",
+  "column_break_18",
+  "serial_no",
+  "batch_size",
   "required_items_section",
   "materials_and_operations_tab",
-  "required_items",
   "operations_section",
   "operations",
   "transfer_material_against",
@@ -586,7 +587,11 @@
   {
    "fieldname": "materials_and_operations_tab",
    "fieldtype": "Tab Break",
-   "label": "Materials & Operations"
+   "label": "Operations"
+  },
+  {
+   "fieldname": "section_break_ndpq",
+   "fieldtype": "Section Break"
   }
  ],
  "icon": "fa fa-cogs",
@@ -594,7 +599,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-03 14:16:35.427731",
+ "modified": "2023-04-06 12:35:12.149827",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0ef51a9..3357b06 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -330,3 +330,4 @@
 # below migration patches should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
 execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
+erpnext.patches.v15_0.enable_all_leads
diff --git a/erpnext/patches/v15_0/enable_all_leads.py b/erpnext/patches/v15_0/enable_all_leads.py
new file mode 100644
index 0000000..c1f2b47
--- /dev/null
+++ b/erpnext/patches/v15_0/enable_all_leads.py
@@ -0,0 +1,8 @@
+import frappe
+
+
+def execute():
+	lead = frappe.qb.DocType("Lead")
+	frappe.qb.update(lead).set(lead.disabled, 0).set(lead.docstatus, 0).where(
+		lead.disabled == 1 and lead.docstatus == 1
+	).run()
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index a07f75d..d943126 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -55,6 +55,14 @@
 	},
 
 	allocate_advances_automatically: function(frm) {
+		frm.trigger('fetch_advances');
+	},
+
+	only_include_allocated_payments: function(frm) {
+		frm.trigger('fetch_advances');
+	},
+
+	fetch_advances: function(frm) {
 		if(frm.doc.allocate_advances_automatically) {
 			frappe.call({
 				doc: frm.doc,
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 8e57ebd..8efc47d 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -135,7 +135,7 @@
 				}
 				else {
 					// allow for '0' qty on Credit/Debit notes
-					let qty = item.qty || me.frm.doc.is_debit_note ? 1 : -1;
+					let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1);
 					item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
 				}
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 8d69ea0..0bd4d91 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1897,20 +1897,60 @@
 	}
 
 	make_payment_entry() {
+		let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry;
+		if(this.has_discount_in_schedule() && !via_journal_entry) {
+			// If early payment discount is applied, ask user for reference date
+			this.prompt_user_for_reference_date();
+		} else {
+			this.make_mapped_payment_entry();
+		}
+	}
+
+	make_mapped_payment_entry(args) {
+		var me = this;
+		args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name };
 		return frappe.call({
-			method: cur_frm.cscript.get_method_for_payment(),
-			args: {
-				"dt": cur_frm.doc.doctype,
-				"dn": cur_frm.doc.name
-			},
+			method: me.get_method_for_payment(),
+			args: args,
 			callback: function(r) {
 				var doclist = frappe.model.sync(r.message);
 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
-				// cur_frm.refresh_fields()
 			}
 		});
 	}
 
+	prompt_user_for_reference_date(){
+		var me = this;
+		frappe.prompt({
+			label: __("Cheque/Reference Date"),
+			fieldname: "reference_date",
+			fieldtype: "Date",
+			reqd: 1,
+		}, (values) => {
+			let args = {
+				"dt": me.frm.doc.doctype,
+				"dn": me.frm.doc.name,
+				"reference_date": values.reference_date
+			}
+			me.make_mapped_payment_entry(args);
+		},
+		__("Reference Date for Early Payment Discount"),
+		__("Continue")
+		);
+	}
+
+	has_discount_in_schedule() {
+		let is_eligible = in_list(
+			["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"],
+			this.frm.doctype
+		);
+		let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
+		if(!is_eligible || !has_payment_schedule) return false;
+
+		let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date);
+		return has_discount;
+	}
+
 	make_quality_inspection() {
 		let data = [];
 		const fields = [
diff --git a/erpnext/public/js/website_utils.js b/erpnext/public/js/website_utils.js
index b541606..2bb5255 100644
--- a/erpnext/public/js/website_utils.js
+++ b/erpnext/public/js/website_utils.js
@@ -3,18 +3,6 @@
 
 if(!window.erpnext) window.erpnext = {};
 
-// Add / update a new Lead / Communication
-// subject, sender, description
-frappe.send_message = function(opts, btn) {
-	return frappe.call({
-		type: "POST",
-		method: "erpnext.templates.utils.send_message",
-		btn: btn,
-		args: opts,
-		callback: opts.callback
-	});
-};
-
 erpnext.subscribe_to_newsletter = function(opts, btn) {
 	return frappe.call({
 		type: "POST",
@@ -24,6 +12,3 @@
 		callback: opts.callback
 	});
 }
-
-// for backward compatibility
-erpnext.send_message = frappe.send_message;
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 107e4a4..b53f339 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -123,7 +123,7 @@
 
 			frm.add_custom_button(__('Accounting Ledger'), function () {
 				frappe.set_route('query-report', 'General Ledger',
-					{party_type: 'Customer', party: frm.doc.name});
+					{party_type: 'Customer', party: frm.doc.name, party_name: frm.doc.customer_name});
 			}, __('View'));
 
 			frm.add_custom_button(__('Pricing Rule'), function () {
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 944a0a6..18336d2 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -11,10 +11,9 @@
 	delete_contact_and_address,
 	load_address_and_contact,
 )
-from frappe.desk.reportview import build_match_conditions, get_filters_cond
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
-from frappe.model.rename_doc import update_linked_doctypes
+from frappe.model.utils.rename_doc import update_linked_doctypes
 from frappe.utils import cint, cstr, flt, get_formatted_email, today
 from frappe.utils.user import get_users_with_role
 
@@ -445,50 +444,6 @@
 	return links
 
 
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
-	from frappe.utils.deprecations import deprecation_warning
-
-	from erpnext.controllers.queries import get_fields
-
-	deprecation_warning(
-		"`get_customer_list` is deprecated and will be removed in version 15. Use `erpnext.controllers.queries.customer_query` instead."
-	)
-
-	fields = ["name", "customer_name", "customer_group", "territory"]
-
-	if frappe.db.get_default("cust_master_name") == "Customer Name":
-		fields = ["name", "customer_group", "territory"]
-
-	fields = get_fields("Customer", fields)
-
-	match_conditions = build_match_conditions("Customer")
-	match_conditions = "and {}".format(match_conditions) if match_conditions else ""
-
-	if filters:
-		filter_conditions = get_filters_cond(doctype, filters, [])
-		match_conditions += "{}".format(filter_conditions)
-
-	return frappe.db.sql(
-		"""
-		select %s
-		from `tabCustomer`
-		where docstatus < 2
-			and (%s like %s or customer_name like %s)
-			{match_conditions}
-		order by
-			case when name like %s then 0 else 1 end,
-			case when customer_name like %s then 0 else 1 end,
-			name, customer_name limit %s, %s
-		""".format(
-			match_conditions=match_conditions
-		)
-		% (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"),
-		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len),
-	)
-
-
 def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0):
 	credit_limit = get_credit_limit(customer, company)
 	if not credit_limit:
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 45ad7d9..045227f 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -29,6 +29,7 @@
   "allow_multiple_items",
   "allow_against_multiple_purchase_orders",
   "allow_sales_order_creation_for_expired_quotation",
+  "dont_reserve_sales_order_qty_on_sales_return",
   "hide_tax_id",
   "enable_discount_accounting"
  ],
@@ -186,6 +187,12 @@
    "fieldname": "over_order_allowance",
    "fieldtype": "Float",
    "label": "Over Order Allowance (%)"
+  },
+  {
+   "default": "0",
+   "fieldname": "dont_reserve_sales_order_qty_on_sales_return",
+   "fieldtype": "Check",
+   "label": "Don't Reserve Sales Order Qty on Sales Return"
   }
  ],
  "icon": "fa fa-cog",
@@ -193,7 +200,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-03-03 11:16:54.333615",
+ "modified": "2023-02-04 12:37:53.380857",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
@@ -222,4 +229,4 @@
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 2fdfcf6..f5432c1 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -36,8 +36,24 @@
 
 		self.make_route()
 		self.validate_item_group_defaults()
+		self.check_item_tax()
 		ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True)
 
+	def check_item_tax(self):
+		"""Check whether Tax Rate is not entered twice for same Tax Type"""
+		check_list = []
+		for d in self.get("taxes"):
+			if d.item_tax_template:
+				if (d.item_tax_template, d.tax_category) in check_list:
+					frappe.throw(
+						_("{0} entered twice {1} in Item Taxes").format(
+							frappe.bold(d.item_tax_template),
+							"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
+						)
+					)
+				else:
+					check_list.append((d.item_tax_template, d.tax_category))
+
 	def on_update(self):
 		NestedSet.on_update(self)
 		invalidate_cache_for(self)
@@ -148,12 +164,17 @@
 
 
 def get_parent_item_groups(item_group_name, from_item=False):
-	base_nav_page = {"name": _("All Products"), "route": "/all-products"}
+	settings = frappe.get_cached_doc("E Commerce Settings")
+
+	if settings.enable_field_filters:
+		base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
+	else:
+		base_nav_page = {"name": _("All Products"), "route": "/all-products"}
 
 	if from_item and frappe.request.environ.get("HTTP_REFERER"):
 		# base page after 'Home' will vary on Item page
 		last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
-		if last_page and last_page == "shop-by-category":
+		if last_page and last_page in ("shop-by-category", "all-products"):
 			base_nav_page_title = " ".join(last_page.split("-")).title()
 			base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}
 
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index f14288b..3b9fe7b 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -6,7 +6,8 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.naming import make_autoname, revert_series_if_last
-from frappe.utils import cint, flt, get_link_to_form
+from frappe.query_builder.functions import CurDate, Sum, Timestamp
+from frappe.utils import cint, flt, get_link_to_form, nowtime
 from frappe.utils.data import add_days
 from frappe.utils.jinja import render_template
 
@@ -176,45 +177,41 @@
 	:param warehouse: Optional - give qty for this warehouse
 	:param item_code: Optional - give qty for this item"""
 
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
 	out = 0
 	if batch_no and warehouse:
-		cond = ""
-		if posting_date and posting_time:
-			cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(
-				posting_date, posting_time
+		query = (
+			frappe.qb.from_(sle)
+			.select(Sum(sle.actual_qty))
+			.where((sle.is_cancelled == 0) & (sle.warehouse == warehouse) & (sle.batch_no == batch_no))
+		)
+
+		if posting_date:
+			if posting_time is None:
+				posting_time = nowtime()
+
+			query = query.where(
+				Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time)
 			)
 
-		out = float(
-			frappe.db.sql(
-				"""select sum(actual_qty)
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(
-					cond
-				),
-				(warehouse, batch_no),
-			)[0][0]
-			or 0
-		)
+		out = query.run(as_list=True)[0][0] or 0
 
 	if batch_no and not warehouse:
-		out = frappe.db.sql(
-			"""select warehouse, sum(actual_qty) as qty
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and batch_no=%s
-			group by warehouse""",
-			batch_no,
-			as_dict=1,
-		)
+		out = (
+			frappe.qb.from_(sle)
+			.select(sle.warehouse, Sum(sle.actual_qty).as_("qty"))
+			.where((sle.is_cancelled == 0) & (sle.batch_no == batch_no))
+			.groupby(sle.warehouse)
+		).run(as_dict=True)
 
 	if not batch_no and item_code and warehouse:
-		out = frappe.db.sql(
-			"""select batch_no, sum(actual_qty) as qty
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and item_code = %s and warehouse=%s
-			group by batch_no""",
-			(item_code, warehouse),
-			as_dict=1,
-		)
+		out = (
+			frappe.qb.from_(sle)
+			.select(sle.batch_no, Sum(sle.actual_qty).as_("qty"))
+			.where((sle.is_cancelled == 0) & (sle.item_code == item_code) & (sle.warehouse == warehouse))
+			.groupby(sle.batch_no)
+		).run(as_dict=True)
 
 	return out
 
@@ -310,40 +307,44 @@
 def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
-	cond = ""
+	batch = frappe.qb.DocType("Batch")
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
+	query = (
+		frappe.qb.from_(batch)
+		.join(sle)
+		.on(batch.batch_id == sle.batch_no)
+		.select(
+			batch.batch_id,
+			Sum(sle.actual_qty).as_("qty"),
+		)
+		.where(
+			(sle.item_code == item_code)
+			& (sle.warehouse == warehouse)
+			& (sle.is_cancelled == 0)
+			& ((batch.expiry_date >= CurDate()) | (batch.expiry_date.isnull()))
+		)
+		.groupby(batch.batch_id)
+		.orderby(batch.expiry_date, batch.creation)
+	)
+
 	if serial_no and frappe.get_cached_value("Item", item_code, "has_batch_no"):
 		serial_nos = get_serial_nos(serial_no)
-		batch = frappe.get_all(
+		batches = frappe.get_all(
 			"Serial No",
 			fields=["distinct batch_no"],
 			filters={"item_code": item_code, "warehouse": warehouse, "name": ("in", serial_nos)},
 		)
 
-		if not batch:
+		if not batches:
 			validate_serial_no_with_batch(serial_nos, item_code)
 
-		if batch and len(batch) > 1:
+		if batches and len(batches) > 1:
 			return []
 
-		cond = " and `tabBatch`.name = %s" % (frappe.db.escape(batch[0].batch_no))
+		query = query.where(batch.name == batches[0].batch_no)
 
-	return frappe.db.sql(
-		"""
-		select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
-		from `tabBatch`
-			join `tabStock Ledger Entry` ignore index (item_code, warehouse)
-				on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
-		where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
-			and `tabStock Ledger Entry`.is_cancelled = 0
-			and (`tabBatch`.expiry_date >= CURRENT_DATE or `tabBatch`.expiry_date IS NULL) {0}
-		group by batch_id
-		order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
-	""".format(
-			cond
-		),
-		(item_code, warehouse),
-		as_dict=True,
-	)
+	return query.run(as_dict=True)
 
 
 def validate_serial_no_with_batch(serial_nos, item_code):
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 903e2af..22d8135 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1180,6 +1180,53 @@
 
 		self.assertTrue(return_dn.docstatus == 1)
 
+	def test_reserve_qty_on_sales_return(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
+		self.reserved_qty_check()
+
+	def test_dont_reserve_qty_on_sales_return(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 1)
+		self.reserved_qty_check()
+
+	def reserved_qty_check(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+		from erpnext.stock.stock_balance import get_reserved_qty
+
+		dont_reserve_qty = frappe.db.get_single_value(
+			"Selling Settings", "dont_reserve_sales_order_qty_on_sales_return"
+		)
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+		qty_to_reserve = 5
+
+		so = make_sales_order(item_code=item, qty=qty_to_reserve)
+
+		# Make qty avl for test.
+		make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, basic_rate=100)
+
+		# Test that item qty has been reserved on submit of sales order.
+		self.assertEqual(get_reserved_qty(item, warehouse), qty_to_reserve)
+
+		dn = make_delivery_note(so.name)
+		dn.save().submit()
+
+		# Test that item qty is no longer reserved since qty has been delivered.
+		self.assertEqual(get_reserved_qty(item, warehouse), 0)
+
+		dn_return = make_return_doc("Delivery Note", dn.name)
+		dn_return.save().submit()
+
+		returned = frappe.get_doc("Delivery Note", dn_return.name)
+		returned.update_prevdoc_status()
+
+		# Test that item qty is not reserved on sales return, if selling setting don't reserve qty is checked.
+		self.assertEqual(get_reserved_qty(item, warehouse), 0 if dont_reserve_qty else qty_to_reserve)
+
+	def tearDown(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index d3ed493..180adee 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -83,6 +83,8 @@
   "actual_qty",
   "installed_qty",
   "item_tax_rate",
+  "column_break_atna",
+  "received_qty",
   "accounting_details_section",
   "expense_account",
   "allow_zero_valuation_rate",
@@ -832,13 +834,27 @@
    "fieldname": "material_request_item",
    "fieldtype": "Data",
    "label": "Material Request Item"
+  },
+  {
+   "fieldname": "column_break_atna",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval: parent.is_internal_customer",
+   "fieldname": "received_qty",
+   "fieldtype": "Float",
+   "label": "Received Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-03-30 23:27:30.943175",
+ "modified": "2023-04-06 09:28:29.182053",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 05a37ee..3cc59be 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -117,7 +117,6 @@
 		self.validate_auto_reorder_enabled_in_stock_settings()
 		self.cant_change()
 		self.validate_item_tax_net_rate_range()
-		set_item_tax_from_hsn_code(self)
 
 		if not self.is_new():
 			self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -352,10 +351,15 @@
 		check_list = []
 		for d in self.get("taxes"):
 			if d.item_tax_template:
-				if d.item_tax_template in check_list:
-					frappe.throw(_("{0} entered twice in Item Tax").format(d.item_tax_template))
+				if (d.item_tax_template, d.tax_category) in check_list:
+					frappe.throw(
+						_("{0} entered twice {1} in Item Taxes").format(
+							frappe.bold(d.item_tax_template),
+							"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
+						)
+					)
 				else:
-					check_list.append(d.item_tax_template)
+					check_list.append((d.item_tax_template, d.tax_category))
 
 	def validate_barcode(self):
 		import barcodenumber
@@ -1316,11 +1320,6 @@
 			frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
 
 
-@erpnext.allow_regional
-def set_item_tax_from_hsn_code(item):
-	pass
-
-
 def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None:
 	for item_default in item_defaults:
 		for doctype, field in [
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index c1abd31..d268cc1 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -65,6 +65,16 @@
 				"percent_join_field": "purchase_invoice",
 				"overflow_type": "receipt",
 			},
+			{
+				"source_dt": "Purchase Receipt Item",
+				"target_dt": "Delivery Note Item",
+				"join_field": "delivery_note_item",
+				"source_field": "received_qty",
+				"target_field": "received_qty",
+				"target_parent_dt": "Delivery Note",
+				"target_ref_field": "qty",
+				"overflow_type": "receipt",
+			},
 		]
 
 		if cint(self.is_return):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index b634146..7567cfe 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1544,6 +1544,72 @@
 		res = get_item_details(args)
 		self.assertEqual(res.get("last_purchase_rate"), 100)
 
+	def test_validate_received_qty_for_internal_pr(self):
+		prepare_data_for_internal_transfer()
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
+		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
+		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
+
+		# Step 1: Create Item
+		item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
+
+		# Step 2: Create Stock Entry (Material Receipt)
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		make_stock_entry(
+			purpose="Material Receipt",
+			item_code=item.name,
+			qty=15,
+			company=company,
+			to_warehouse=from_warehouse,
+		)
+
+		# Step 3: Create Delivery Note with Internal Customer
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+		dn = create_delivery_note(
+			item_code=item.name,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			expense_account="Cost of Goods Sold - TCP1",
+			qty=10,
+			rate=100,
+			warehouse=from_warehouse,
+			target_warehouse=target_warehouse,
+		)
+
+		# Step 4: Create Internal Purchase Receipt
+		from erpnext.controllers.status_updater import OverAllowanceError
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+
+		pr = make_inter_company_purchase_receipt(dn.name)
+		pr.items[0].qty = 15
+		pr.items[0].from_warehouse = target_warehouse
+		pr.items[0].warehouse = to_warehouse
+		pr.items[0].rejected_warehouse = from_warehouse
+		pr.save()
+
+		self.assertRaises(OverAllowanceError, pr.submit)
+
+		# Step 5: Test Over Receipt Allowance
+		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50)
+
+		make_stock_entry(
+			purpose="Material Transfer",
+			item_code=item.name,
+			qty=5,
+			company=company,
+			from_warehouse=from_warehouse,
+			to_warehouse=target_warehouse,
+		)
+
+		pr.submit()
+
+		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 9c0f1fc..bc5533f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -27,7 +27,6 @@
   "set_posting_time",
   "inspection_required",
   "apply_putaway_rule",
-  "items_tab",
   "bom_info_section",
   "from_bom",
   "use_multi_level_bom",
@@ -256,7 +255,7 @@
    "description": "As per Stock UOM",
    "fieldname": "fg_completed_qty",
    "fieldtype": "Float",
-   "label": "For Quantity",
+   "label": "Finished Good Quantity ",
    "oldfieldname": "fg_completed_qty",
    "oldfieldtype": "Currency",
    "print_hide": 1
@@ -612,11 +611,7 @@
    "read_only": 1
   },
   {
-   "fieldname": "items_tab",
-   "fieldtype": "Tab Break",
-   "label": "Items"
-  },
-  {
+   "collapsible": 1,
    "fieldname": "bom_info_section",
    "fieldtype": "Section Break",
    "label": "BOM Info"
@@ -644,8 +639,10 @@
    "oldfieldtype": "Section Break"
   },
   {
+   "collapsible": 1,
    "fieldname": "section_break_7qsm",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Process Loss"
   },
   {
    "depends_on": "process_loss_percentage",
@@ -677,7 +674,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-03 16:02:50.741816",
+ "modified": "2023-04-06 12:42:56.673180",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 3f6a2c8..482b103 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -4,7 +4,8 @@
 from typing import Optional
 
 import frappe
-from frappe import _, msgprint
+from frappe import _, bold, msgprint
+from frappe.query_builder.functions import Sum
 from frappe.utils import cint, cstr, flt
 
 import erpnext
@@ -89,7 +90,7 @@
 
 				if item_dict.get("serial_nos"):
 					item.current_serial_no = item_dict.get("serial_nos")
-					if self.purpose == "Stock Reconciliation" and not item.serial_no:
+					if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty:
 						item.serial_no = item.current_serial_no
 
 				item.current_qty = item_dict.get("qty")
@@ -140,6 +141,14 @@
 
 			self.validate_item(row.item_code, row)
 
+			if row.serial_no and not row.qty:
+				self.validation_messages.append(
+					_get_msg(
+						row_num,
+						f"Quantity should not be zero for the {bold(row.item_code)} since serial nos are specified",
+					)
+				)
+
 			# validate warehouse
 			if not frappe.db.get_value("Warehouse", row.warehouse):
 				self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system")))
@@ -561,6 +570,54 @@
 		else:
 			self._cancel()
 
+	def recalculate_current_qty(self, item_code, batch_no):
+		for row in self.items:
+			if not (row.item_code == item_code and row.batch_no == batch_no):
+				continue
+
+			row.current_qty = get_batch_qty_for_stock_reco(item_code, row.warehouse, batch_no)
+
+			qty, val_rate = get_stock_balance(
+				item_code,
+				row.warehouse,
+				self.posting_date,
+				self.posting_time,
+				with_valuation_rate=True,
+			)
+
+			row.current_valuation_rate = val_rate
+
+			row.db_set(
+				{
+					"current_qty": row.current_qty,
+					"current_valuation_rate": row.current_valuation_rate,
+					"current_amount": flt(row.current_qty * row.current_valuation_rate),
+				}
+			)
+
+
+def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no):
+	ledger = frappe.qb.DocType("Stock Ledger Entry")
+
+	query = (
+		frappe.qb.from_(ledger)
+		.select(
+			Sum(ledger.actual_qty).as_("batch_qty"),
+		)
+		.where(
+			(ledger.item_code == item_code)
+			& (ledger.warehouse == warehouse)
+			& (ledger.docstatus == 1)
+			& (ledger.is_cancelled == 0)
+			& (ledger.batch_no == batch_no)
+		)
+		.groupby(ledger.batch_no)
+	)
+
+	sle = query.run(as_dict=True)
+
+	return flt(sle[0].batch_qty) if sle else 0
+
 
 @frappe.whitelist()
 def get_items(
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 489ec6e..2cf3797 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -35,7 +35,14 @@
 
 
 @frappe.whitelist()
-def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True):
+def get_item_details(
+	args,
+	doc=None,
+	for_validate=False,
+	overwrite_warehouse=True,
+	return_basic_details=False,
+	basic_details=None,
+):
 	"""
 	args = {
 	        "item_code": "",
@@ -73,7 +80,13 @@
 		if doc.get("doctype") == "Purchase Invoice":
 			args["bill_date"] = doc.get("bill_date")
 
-	out = get_basic_details(args, item, overwrite_warehouse)
+	if not basic_details:
+		out = get_basic_details(args, item, overwrite_warehouse)
+	else:
+		out = basic_details
+
+	basic_details = out.copy()
+
 	get_item_tax_template(args, item, out)
 	out["item_tax_rate"] = get_item_tax_map(
 		args.company,
@@ -141,7 +154,11 @@
 		out.amount = flt(args.qty) * flt(out.rate)
 
 	out = remove_standard_fields(out)
-	return out
+
+	if return_basic_details:
+		return out, basic_details
+	else:
+		return out
 
 
 def remove_standard_fields(details):
@@ -620,7 +637,9 @@
 				taxes_with_no_validity.append(tax)
 
 	if taxes_with_validity:
-		taxes = sorted(taxes_with_validity, key=lambda i: i.valid_from, reverse=True)
+		taxes = sorted(
+			taxes_with_validity, key=lambda i: i.valid_from or tax.maximum_net_rate, reverse=True
+		)
 	else:
 		taxes = taxes_with_no_validity
 
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index a6a630f..d6febfe 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -7,7 +7,7 @@
 
 import frappe
 from frappe import _
-from frappe.query_builder.functions import CombineDatetime
+from frappe.query_builder.functions import Coalesce, CombineDatetime
 from frappe.utils import cint, date_diff, flt, getdate
 from frappe.utils.nestedset import get_descendants_of
 
@@ -331,6 +331,34 @@
 	return query.run(as_dict=True)
 
 
+def get_opening_vouchers(to_date):
+	opening_vouchers = {"Stock Entry": [], "Stock Reconciliation": []}
+
+	se = frappe.qb.DocType("Stock Entry")
+	sr = frappe.qb.DocType("Stock Reconciliation")
+
+	vouchers_data = (
+		frappe.qb.from_(
+			(
+				frappe.qb.from_(se)
+				.select(se.name, Coalesce("Stock Entry").as_("voucher_type"))
+				.where((se.docstatus == 1) & (se.posting_date <= to_date) & (se.is_opening == "Yes"))
+			)
+			+ (
+				frappe.qb.from_(sr)
+				.select(sr.name, Coalesce("Stock Reconciliation").as_("voucher_type"))
+				.where((sr.docstatus == 1) & (sr.posting_date <= to_date) & (sr.purpose == "Opening Stock"))
+			)
+		).select("voucher_type", "name")
+	).run(as_dict=True)
+
+	if vouchers_data:
+		for d in vouchers_data:
+			opening_vouchers[d.voucher_type].append(d.name)
+
+	return opening_vouchers
+
+
 def get_inventory_dimension_fields():
 	return [dimension.fieldname for dimension in get_inventory_dimensions()]
 
@@ -339,9 +367,8 @@
 	iwb_map = {}
 	from_date = getdate(filters.get("from_date"))
 	to_date = getdate(filters.get("to_date"))
-
+	opening_vouchers = get_opening_vouchers(to_date)
 	float_precision = cint(frappe.db.get_default("float_precision")) or 3
-
 	inventory_dimensions = get_inventory_dimension_fields()
 
 	for d in sle:
@@ -372,11 +399,7 @@
 
 		value_diff = flt(d.stock_value_difference)
 
-		if d.posting_date < from_date or (
-			d.posting_date == from_date
-			and d.voucher_type == "Stock Reconciliation"
-			and frappe.db.get_value("Stock Reconciliation", d.voucher_no, "purpose") == "Opening Stock"
-		):
+		if d.posting_date < from_date or d.voucher_no in opening_vouchers.get(d.voucher_type, []):
 			qty_dict.opening_qty += qty_diff
 			qty_dict.opening_val += value_diff
 
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index da17cde..77bc4e0 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -34,6 +34,9 @@
 		conversion_factors.append(0)
 
 	actual_qty = stock_value = 0
+	if opening_row:
+		actual_qty = opening_row.get("qty_after_transaction")
+		stock_value = opening_row.get("stock_value")
 
 	available_serial_nos = {}
 	inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters)
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 439ed7a..e3cbb43 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -94,10 +94,13 @@
 
 
 def get_reserved_qty(item_code, warehouse):
+	dont_reserve_on_return = frappe.get_cached_value(
+		"Selling Settings", "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return"
+	)
 	reserved_qty = frappe.db.sql(
-		"""
+		f"""
 		select
-			sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty))
+			sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty - if(dont_reserve_qty_on_return, so_item_returned_qty, 0)) / so_item_qty))
 		from
 			(
 				(select
@@ -112,6 +115,12 @@
 						where name = dnpi.parent_detail_docname
 						and delivered_by_supplier = 0
 					) as so_item_delivered_qty,
+					(
+						select returned_qty from `tabSales Order Item`
+						where name = dnpi.parent_detail_docname
+						and delivered_by_supplier = 0
+					) as so_item_returned_qty,
+					{dont_reserve_on_return} as dont_reserve_qty_on_return,
 					parent, name
 				from
 				(
@@ -125,7 +134,9 @@
 				) dnpi)
 			union
 				(select stock_qty as dnpi_qty, qty as so_item_qty,
-					delivered_qty as so_item_delivered_qty, parent, name
+					delivered_qty as so_item_delivered_qty,
+					returned_qty as so_item_returned_qty,
+					{dont_reserve_on_return}, parent, name
 				from `tabSales Order Item` so_item
 				where item_code = %s and warehouse = %s
 				and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 08fc6fb..c954bef 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1337,6 +1337,9 @@
 	next_stock_reco_detail = get_next_stock_reco(args)
 	if next_stock_reco_detail:
 		detail = next_stock_reco_detail[0]
+		if detail.batch_no:
+			regenerate_sle_for_batch_stock_reco(detail)
+
 		# add condition to update SLEs before this date & time
 		datetime_limit_condition = get_datetime_limit_condition(detail)
 
@@ -1364,6 +1367,16 @@
 	validate_negative_qty_in_future_sle(args, allow_negative_stock)
 
 
+def regenerate_sle_for_batch_stock_reco(detail):
+	doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no)
+	doc.docstatus = 2
+	doc.update_stock_ledger()
+
+	doc.recalculate_current_qty(detail.item_code, detail.batch_no)
+	doc.docstatus = 1
+	doc.update_stock_ledger()
+
+
 def get_stock_reco_qty_shift(args):
 	stock_reco_qty_shift = 0
 	if args.get("is_cancelled"):
@@ -1393,7 +1406,7 @@
 	return frappe.db.sql(
 		"""
 		select
-			name, posting_date, posting_time, creation, voucher_no
+			name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty
 		from
 			`tabStock Ledger Entry`
 		where
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 95dbc83..4f8e045 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -245,17 +245,17 @@
 					item.expense_account = expense_account
 
 	def update_status(self, status=None, update_modified=False):
-		if self.docstatus >= 1 and not status:
-			if self.docstatus == 1:
+		if not status:
+			if self.docstatus == 0:
+				status = "Draft"
+			elif self.docstatus == 1:
+				status = "Completed"
 				if self.is_return:
 					status = "Return"
 					return_against = frappe.get_doc("Subcontracting Receipt", self.return_against)
 					return_against.run_method("update_status")
-				else:
-					if self.per_returned == 100:
-						status = "Return Issued"
-					elif self.status == "Draft":
-						status = "Completed"
+				elif self.per_returned == 100:
+					status = "Return Issued"
 			elif self.docstatus == 2:
 				status = "Cancelled"
 
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index 48b4480..57750a5 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -6,13 +6,12 @@
 
 
 @frappe.whitelist(allow_guest=True)
-def send_message(subject="Website Query", message="", sender="", status="Open"):
+def send_message(sender, message, subject="Website Query"):
 	from frappe.www.contact import send_message as website_send_message
 
+	website_send_message(sender, message, subject)
+
 	lead = customer = None
-
-	website_send_message(subject, message, sender)
-
 	customer = frappe.db.sql(
 		"""select distinct dl.link_name from `tabDynamic Link` dl
 		left join `tabContact` c on dl.parent=c.name where dl.link_doctype='Customer'
@@ -58,5 +57,3 @@
 		}
 	)
 	comm.insert(ignore_permissions=True)
-
-	return "okay"
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 9599eed..57556f3 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1875,6 +1875,7 @@
 Part-time,Teilzeit,
 Partially Depreciated,Teilweise abgeschrieben,
 Partially Received,Teilweise erhalten,
+Partly Paid,Teilweise bezahlt,
 Party,Partei,
 Party Name,Name der Partei,
 Party Type,Partei-Typ,
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 21a0a55..7eba35d 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -58,11 +58,11 @@
 
 	def compare_values(self, ref_doc, fields, doc=None):
 		for reference_doctype, ref_dn_list in ref_doc.items():
+			prev_doc_detail_map = self.get_prev_doc_reference_details(
+				ref_dn_list, reference_doctype, fields
+			)
 			for reference_name in ref_dn_list:
-				prevdoc_values = frappe.db.get_value(
-					reference_doctype, reference_name, [d[0] for d in fields], as_dict=1
-				)
-
+				prevdoc_values = prev_doc_detail_map.get(reference_name)
 				if not prevdoc_values:
 					frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
 
@@ -70,6 +70,19 @@
 					if prevdoc_values[field] is not None and field not in self.exclude_fields:
 						self.validate_value(field, condition, prevdoc_values[field], doc)
 
+	def get_prev_doc_reference_details(self, reference_names, reference_doctype, fields):
+		prev_doc_detail_map = {}
+		details = frappe.get_all(
+			reference_doctype,
+			filters={"name": ("in", reference_names)},
+			fields=["name"] + [d[0] for d in fields],
+		)
+
+		for d in details:
+			prev_doc_detail_map.setdefault(d.name, d)
+
+		return prev_doc_detail_map
+
 	def validate_rate_with_reference_doc(self, ref_details):
 		if self.get("is_internal_supplier"):
 			return
@@ -77,23 +90,23 @@
 		buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
 
 		if self.doctype in buying_doctypes:
-			action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
-			settings_doc = "Buying Settings"
+			action, role_allowed_to_override = frappe.get_cached_value(
+				"Buying Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
+			)
 		else:
-			action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
-			settings_doc = "Selling Settings"
+			action, role_allowed_to_override = frappe.get_cached_value(
+				"Selling Settings", "None", ["maintain_same_rate_action", "role_to_override_stop_action"]
+			)
 
 		for ref_dt, ref_dn_field, ref_link_field in ref_details:
+			reference_names = [d.get(ref_link_field) for d in self.get("items") if d.get(ref_link_field)]
+			reference_details = self.get_reference_details(reference_names, ref_dt + " Item")
 			for d in self.get("items"):
 				if d.get(ref_link_field):
-					ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
+					ref_rate = reference_details.get(d.get(ref_link_field))
 
 					if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
 						if action == "Stop":
-							role_allowed_to_override = frappe.db.get_single_value(
-								settings_doc, "role_to_override_stop_action"
-							)
-
 							if role_allowed_to_override not in frappe.get_roles():
 								frappe.throw(
 									_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
@@ -109,6 +122,16 @@
 								indicator="orange",
 							)
 
+	def get_reference_details(self, reference_names, reference_doctype):
+		return frappe._dict(
+			frappe.get_all(
+				reference_doctype,
+				filters={"name": ("in", reference_names)},
+				fields=["name", "rate"],
+				as_list=1,
+			)
+		)
+
 	def get_link_filters(self, for_doctype):
 		if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
 			fieldname = self.prev_link_mapper[for_doctype]["fieldname"]
@@ -186,12 +209,15 @@
 			for f in qty_fields:
 				qty = d.get(f)
 				if qty:
-					if abs(cint(qty) - flt(qty)) > 0.0000001:
+					if abs(cint(qty) - flt(qty, d.precision(f))) > 0.0000001:
 						frappe.throw(
 							_(
 								"Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}."
 							).format(
-								qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))
+								flt(qty, d.precision(f)),
+								d.idx,
+								frappe.bold(_("Must be Whole Number")),
+								frappe.bold(d.get(uom_field)),
 							),
 							UOMMustBeIntegerError,
 						)
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
index 219747c..913c183 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -53,6 +53,7 @@
 
 def get_category_records(categories: list):
 	categorical_data = {}
+	website_item_meta = frappe.get_meta("Website Item", cached=True)
 
 	for c in categories:
 		if c == "item_group":
@@ -64,7 +65,16 @@
 
 			continue
 
-		doctype = frappe.unscrub(c)
+		field_type = website_item_meta.get_field(c).fieldtype
+
+		if field_type == "Table MultiSelect":
+			child_doc = website_item_meta.get_field(c).options
+			for field in frappe.get_meta(child_doc, cached=True).fields:
+				if field.fieldtype == "Link" and field.reqd:
+					doctype = field.options
+		else:
+			doctype = website_item_meta.get_field(c).options
+
 		fields = ["name"]
 
 		try: