Merge branch 'develop' of https://github.com/frappe/erpnext into tds_purchase_invoice
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
index 63cc465..7e57c2f 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.js
@@ -4,6 +4,23 @@
 frappe.ui.form.on("Bank Clearance", {
 	setup: function(frm) {
 		frm.add_fetch("account", "account_currency", "account_currency");
+
+		frm.set_query("account", function() {
+			return {
+				"filters": {
+					"account_type": ["in",["Bank","Cash"]],
+					"is_group": 0,
+				}
+			};
+		});
+
+		frm.set_query("bank_account", function () {
+			return {
+				filters: {
+					'is_company_account': 1
+				},
+			};
+		});
 	},
 
 	onload: function(frm) {
@@ -12,14 +29,7 @@
 			locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
 		frm.set_value("account", default_bank_account);
 
-		frm.set_query("account", function() {
-			return {
-				"filters": {
-					"account_type": ["in",["Bank","Cash"]],
-					"is_group": 0
-				}
-			};
-		});
+
 
 		frm.set_value("from_date", frappe.datetime.month_start());
 		frm.set_value("to_date", frappe.datetime.month_end());
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7f245fd..9487489 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -995,7 +995,9 @@
 		if self.payment_type in ("Receive", "Pay") and self.party:
 			for d in self.get("references"):
 				if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
-					frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
+					frappe.get_doc(
+						d.reference_doctype, d.reference_name, for_update=True
+					).set_total_advance_paid()
 
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		self.reference_no = reference_doc.name
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 0a39a62..5c1cb0d 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -215,6 +215,7 @@
    "reqd": 1
   },
   {
+   "default": "1",
    "depends_on": "eval:doc.uom != doc.stock_uom",
    "fieldname": "conversion_factor",
    "fieldtype": "Float",
@@ -821,6 +822,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "section_break_26",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -878,7 +880,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-10-12 03:37:29.032732",
+ "modified": "2022-10-26 16:05:37.304788",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index afd5a59..0c03c55 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -2017,6 +2017,9 @@
 			update_address(
 				target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
 			)
+			update_address(
+				target_doc, "billing_address", "billing_address_display", source_doc.customer_address
+			)
 
 			if currency:
 				target_doc.currency = currency
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index a307a6c..7f1a1ec 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -247,6 +247,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "discount_and_margin",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -876,7 +877,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-10-10 20:57:38.340026",
+ "modified": "2022-10-26 11:38:36.119339",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
index a95e0a9..f3acdc5 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.py
@@ -3,6 +3,7 @@
 
 
 import frappe
+from dateutil import relativedelta
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import date_diff, flt, get_first_day, get_last_day, getdate
@@ -49,7 +50,7 @@
 		start_date = getdate(start_date)
 		end_date = getdate(end_date)
 
-		no_of_months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) + 1
+		no_of_months = relativedelta.relativedelta(end_date, start_date).months + 1
 		cost = plan.cost * no_of_months
 
 		# Adjust cost if start or end date is not month start or end
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 7cf14e6..e1a30a4 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -51,6 +51,8 @@
 				} else {
 					frappe.query_report.set_filter_value('tax_id', "");
 				}
+
+				frappe.query_report.refresh();
 			}
 		},
 		{
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 8557c03..f2ee1eb 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -748,7 +748,7 @@
 
 		self.add_accounting_dimensions_filters()
 
-	def get_cost_center_conditions(self, conditions):
+	def get_cost_center_conditions(self):
 		lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
 		cost_center_list = [
 			center.name
diff --git a/erpnext/accounts/report/payment_ledger/__init__.py b/erpnext/accounts/report/payment_ledger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/__init__.py
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js
new file mode 100644
index 0000000..9779844
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js
@@ -0,0 +1,59 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+function get_filters() {
+	let filters = [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		},
+		{
+			"fieldname":"period_start_date",
+			"label": __("Start Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+		},
+		{
+			"fieldname":"period_end_date",
+			"label": __("End Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.get_today()
+		},
+		{
+			"fieldname":"account",
+			"label": __("Account"),
+			"fieldtype": "MultiSelectList",
+			"options": "Account",
+			get_data: function(txt) {
+				return frappe.db.get_link_options('Account', txt, {
+					company: frappe.query_report.get_filter_value("company")
+				});
+			}
+		},
+		{
+			"fieldname":"voucher_no",
+			"label": __("Voucher No"),
+			"fieldtype": "Data",
+			"width": 100,
+		},
+		{
+			"fieldname":"against_voucher_no",
+			"label": __("Against Voucher No"),
+			"fieldtype": "Data",
+			"width": 100,
+		},
+
+	]
+	return filters;
+}
+
+frappe.query_reports["Payment Ledger"] = {
+	"filters": get_filters()
+};
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.json b/erpnext/accounts/report/payment_ledger/payment_ledger.json
new file mode 100644
index 0000000..716329f
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2022-06-06 08:50:43.933708",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2022-06-06 08:50:43.933708",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Ledger",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Payment Ledger Entry",
+ "report_name": "Payment Ledger",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py
new file mode 100644
index 0000000..e470c27
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py
@@ -0,0 +1,222 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from collections import OrderedDict
+
+import frappe
+from frappe import _, qb
+from frappe.query_builder import Criterion
+
+
+class PaymentLedger(object):
+	def __init__(self, filters=None):
+		self.filters = filters
+		self.columns, self.data = [], []
+		self.voucher_dict = OrderedDict()
+		self.voucher_amount = []
+		self.ple = qb.DocType("Payment Ledger Entry")
+
+	def init_voucher_dict(self):
+
+		if self.voucher_amount:
+			s = set()
+			# build  a set of unique vouchers
+			for ple in self.voucher_amount:
+				key = (ple.voucher_type, ple.voucher_no, ple.party)
+				s.add(key)
+
+			# for each unique vouchers, initialize +/- list
+			for key in s:
+				self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
+
+			# for each ple, using against voucher and amount, assign it to +/- list
+			# group by against voucher
+			for ple in self.voucher_amount:
+				against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+				target = None
+				if self.voucher_dict.get(against_key):
+					if ple.amount > 0:
+						target = self.voucher_dict.get(against_key).increase
+					else:
+						target = self.voucher_dict.get(against_key).decrease
+
+				# this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
+				# need to somehow include the stray entries as well.
+				if target is not None:
+					entry = frappe._dict(
+						company=ple.company,
+						account=ple.account,
+						party_type=ple.party_type,
+						party=ple.party,
+						voucher_type=ple.voucher_type,
+						voucher_no=ple.voucher_no,
+						against_voucher_type=ple.against_voucher_type,
+						against_voucher_no=ple.against_voucher_no,
+						amount=ple.amount,
+						currency=ple.account_currency,
+					)
+
+					if self.filters.include_account_currency:
+						entry["amount_in_account_currency"] = ple.amount_in_account_currency
+
+					target.append(entry)
+
+	def build_data(self):
+		self.data.clear()
+
+		for value in self.voucher_dict.values():
+			voucher_data = []
+			if value.increase != []:
+				voucher_data.extend(value.increase)
+			if value.decrease != []:
+				voucher_data.extend(value.decrease)
+
+			if voucher_data:
+				# balance row
+				total = 0
+				total_in_account_currency = 0
+
+				for x in voucher_data:
+					total += x.amount
+					if self.filters.include_account_currency:
+						total_in_account_currency += x.amount_in_account_currency
+
+				entry = frappe._dict(
+					against_voucher_no="Outstanding:",
+					amount=total,
+					currency=voucher_data[0].currency,
+				)
+
+				if self.filters.include_account_currency:
+					entry["amount_in_account_currency"] = total_in_account_currency
+
+				voucher_data.append(entry)
+
+				# empty row
+				voucher_data.append(frappe._dict())
+				self.data.extend(voucher_data)
+
+	def build_conditions(self):
+		self.conditions = []
+
+		if self.filters.company:
+			self.conditions.append(self.ple.company == self.filters.company)
+
+		if self.filters.account:
+			self.conditions.append(self.ple.account.isin(self.filters.account))
+
+		if self.filters.period_start_date:
+			self.conditions.append(self.ple.posting_date.gte(self.filters.period_start_date))
+
+		if self.filters.period_end_date:
+			self.conditions.append(self.ple.posting_date.lte(self.filters.period_end_date))
+
+		if self.filters.voucher_no:
+			self.conditions.append(self.ple.voucher_no == self.filters.voucher_no)
+
+		if self.filters.against_voucher_no:
+			self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
+
+	def get_data(self):
+		ple = self.ple
+
+		self.build_conditions()
+
+		# fetch data from table
+		self.voucher_amount = (
+			qb.from_(ple)
+			.select(ple.star)
+			.where(ple.delinked == 0)
+			.where(Criterion.all(self.conditions))
+			.run(as_dict=True)
+		)
+
+	def get_columns(self):
+		options = None
+		self.columns.append(
+			dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
+		)
+
+		self.columns.append(
+			dict(label=_("Account"), fieldname="account", fieldtype="data", options=options, width="100")
+		)
+
+		self.columns.append(
+			dict(
+				label=_("Party Type"), fieldname="party_type", fieldtype="data", options=options, width="100"
+			)
+		)
+		self.columns.append(
+			dict(label=_("Party"), fieldname="party", fieldtype="data", options=options, width="100")
+		)
+		self.columns.append(
+			dict(
+				label=_("Voucher Type"),
+				fieldname="voucher_type",
+				fieldtype="data",
+				options=options,
+				width="100",
+			)
+		)
+		self.columns.append(
+			dict(
+				label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
+			)
+		)
+		self.columns.append(
+			dict(
+				label=_("Against Voucher Type"),
+				fieldname="against_voucher_type",
+				fieldtype="data",
+				options=options,
+				width="100",
+			)
+		)
+		self.columns.append(
+			dict(
+				label=_("Against Voucher No"),
+				fieldname="against_voucher_no",
+				fieldtype="data",
+				options=options,
+				width="100",
+			)
+		)
+		self.columns.append(
+			dict(
+				label=_("Amount"),
+				fieldname="amount",
+				fieldtype="Currency",
+				options="Company:company:default_currency",
+				width="100",
+			)
+		)
+
+		if self.filters.include_account_currency:
+			self.columns.append(
+				dict(
+					label=_("Amount in Account Currency"),
+					fieldname="amount_in_account_currency",
+					fieldtype="Currency",
+					options="currency",
+					width="100",
+				)
+			)
+		self.columns.append(
+			dict(label=_("Currency"), fieldname="currency", fieldtype="Currency", hidden=True)
+		)
+
+	def run(self):
+		self.get_columns()
+		self.get_data()
+
+		# initialize dictionary and group using against voucher
+		self.init_voucher_dict()
+
+		# convert dictionary to list and add balance rows
+		self.build_data()
+
+		return self.columns, self.data
+
+
+def execute(filters=None):
+	return PaymentLedger(filters).run()
diff --git a/erpnext/accounts/report/payment_ledger/test_payment_ledger.py b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py
new file mode 100644
index 0000000..5ae9b87
--- /dev/null
+++ b/erpnext/accounts/report/payment_ledger/test_payment_ledger.py
@@ -0,0 +1,65 @@
+import unittest
+
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.report.payment_ledger.payment_ledger import execute
+
+
+class TestPaymentLedger(FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.cleanup()
+
+	def cleanup(self):
+		doctypes = []
+		doctypes.append(qb.DocType("GL Entry"))
+		doctypes.append(qb.DocType("Payment Ledger Entry"))
+		doctypes.append(qb.DocType("Sales Invoice"))
+		doctypes.append(qb.DocType("Payment Entry"))
+
+		for doctype in doctypes:
+			qb.from_(doctype).delete().where(doctype.company == self.company).run()
+
+	def create_company(self):
+		name = "Test Payment Ledger"
+		company = None
+		if frappe.db.exists("Company", name):
+			company = frappe.get_doc("Company", name)
+		else:
+			company = frappe.get_doc(
+				{
+					"doctype": "Company",
+					"company_name": name,
+					"country": "India",
+					"default_currency": "INR",
+					"create_chart_of_accounts_based_on": "Standard Template",
+					"chart_of_accounts": "Standard",
+				}
+			)
+			company = company.save()
+		self.company = company.name
+		self.cost_center = company.cost_center
+		self.warehouse = "All Warehouses" + " - " + company.abbr
+		self.income_account = company.default_income_account
+		self.expense_account = company.default_expense_account
+		self.debit_to = company.default_receivable_account
+
+	def test_unpaid_invoice_outstanding(self):
+		sinv = create_sales_invoice(
+			company=self.company,
+			debit_to=self.debit_to,
+			expense_account=self.expense_account,
+			cost_center=self.cost_center,
+			income_account=self.income_account,
+			warehouse=self.warehouse,
+		)
+		pe = get_payment_entry(sinv.doctype, sinv.name).save().submit()
+
+		filters = frappe._dict({"company": self.company})
+		columns, data = execute(filters=filters)
+		outstanding = [x for x in data if x.get("against_voucher_no") == "Outstanding:"]
+		self.assertEqual(outstanding[0].get("amount"), 0)
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 82e92e8..b8203bd 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -777,6 +777,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "discount_and_margin_section",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -894,7 +895,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-09-07 11:12:38.634976",
+ "modified": "2022-10-26 16:47:41.364387",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index 5572268..e2dbf21 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -3,6 +3,7 @@
 
 
 import frappe
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.test_runner import make_test_records
 
 from erpnext.accounts.party import get_due_date
@@ -152,6 +153,40 @@
 		# Rollback
 		address.delete()
 
+	def test_serach_fields_for_supplier(self):
+		from erpnext.controllers.queries import supplier_query
+
+		supplier_name = create_supplier(supplier_name="Test Supplier 1").name
+
+		make_property_setter(
+			"Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype"
+		)
+
+		data = supplier_query(
+			"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
+		)
+
+		self.assertEqual(data[0].name, supplier_name)
+		self.assertEqual(data[0].supplier_group, "Services")
+		self.assertTrue("supplier_type" not in data[0])
+
+		make_property_setter(
+			"Supplier",
+			None,
+			"search_fields",
+			"supplier_group, supplier_type",
+			"Data",
+			for_doctype="Doctype",
+		)
+		data = supplier_query(
+			"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
+		)
+
+		self.assertEqual(data[0].name, supplier_name)
+		self.assertEqual(data[0].supplier_group, "Services")
+		self.assertEqual(data[0].supplier_type, "Company")
+		self.assertTrue("supplier_type" in data[0])
+
 
 def create_supplier(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 8eae0a0..3bdc017 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -78,18 +78,16 @@
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
-def customer_query(doctype, txt, searchfield, start, page_len, filters):
+def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
 	doctype = "Customer"
 	conditions = []
 	cust_master_name = frappe.defaults.get_user_default("cust_master_name")
 
-	if cust_master_name == "Customer Name":
-		fields = ["name", "customer_group", "territory"]
-	else:
-		fields = ["name", "customer_name", "customer_group", "territory"]
+	fields = ["name"]
+	if cust_master_name != "Customer Name":
+		fields = ["customer_name"]
 
 	fields = get_fields(doctype, fields)
-
 	searchfields = frappe.get_meta(doctype).get_search_fields()
 	searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
 
@@ -112,20 +110,20 @@
 			}
 		),
 		{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+		as_dict=as_dict,
 	)
 
 
 # searches for supplier
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
-def supplier_query(doctype, txt, searchfield, start, page_len, filters):
+def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
 	doctype = "Supplier"
 	supp_master_name = frappe.defaults.get_user_default("supp_master_name")
 
-	if supp_master_name == "Supplier Name":
-		fields = ["name", "supplier_group"]
-	else:
-		fields = ["name", "supplier_name", "supplier_group"]
+	fields = ["name"]
+	if supp_master_name != "Supplier Name":
+		fields = ["supplier_name"]
 
 	fields = get_fields(doctype, fields)
 
@@ -145,6 +143,7 @@
 			**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
 		),
 		{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
+		as_dict=as_dict,
 	)
 
 
diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js
index 38328e6..20e2b0b 100644
--- a/erpnext/loan_management/doctype/loan/loan.js
+++ b/erpnext/loan_management/doctype/loan/loan.js
@@ -61,6 +61,10 @@
 	},
 
 	refresh: function (frm) {
+		if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") {
+			frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date");
+		}
+
 		if (frm.doc.docstatus == 1) {
 			if (["Disbursed", "Partially Disbursed"].includes(frm.doc.status) && (!frm.doc.repay_from_salary)) {
 				frm.add_custom_button(__('Request Loan Closure'), function() {
@@ -103,6 +107,14 @@
 		frm.trigger("toggle_fields");
 	},
 
+	repayment_schedule_type: function(frm) {
+		if (frm.doc.repayment_schedule_type == "Pro-rated calendar months") {
+			frm.set_df_property("repayment_start_date", "label", "Interest Calculation Start Date");
+		} else {
+			frm.set_df_property("repayment_start_date", "label", "Repayment Start Date");
+		}
+	},
+
 	loan_type: function(frm) {
 		frm.toggle_reqd("repayment_method", frm.doc.is_term_loan);
 		frm.toggle_display("repayment_method", frm.doc.is_term_loan);
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index 47488f4..dc8b03e 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -18,6 +18,7 @@
   "status",
   "section_break_8",
   "loan_type",
+  "repayment_schedule_type",
   "loan_amount",
   "rate_of_interest",
   "is_secured_loan",
@@ -158,7 +159,8 @@
    "depends_on": "is_term_loan",
    "fieldname": "repayment_start_date",
    "fieldtype": "Date",
-   "label": "Repayment Start Date"
+   "label": "Repayment Start Date",
+   "mandatory_depends_on": "is_term_loan"
   },
   {
    "fieldname": "column_break_11",
@@ -402,12 +404,20 @@
    "fieldname": "is_npa",
    "fieldtype": "Check",
    "label": "Is NPA"
+  },
+  {
+   "depends_on": "is_term_loan",
+   "fetch_from": "loan_type.repayment_schedule_type",
+   "fieldname": "repayment_schedule_type",
+   "fieldtype": "Data",
+   "label": "Repayment Schedule Type",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-07-12 11:50:31.957360",
+ "modified": "2022-09-30 10:36:47.902903",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index d84eef6..0c9c97f 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -7,7 +7,16 @@
 
 import frappe
 from frappe import _
-from frappe.utils import add_months, flt, get_last_day, getdate, now_datetime, nowdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	date_diff,
+	flt,
+	get_last_day,
+	getdate,
+	now_datetime,
+	nowdate,
+)
 
 import erpnext
 from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry
@@ -107,30 +116,81 @@
 		if not self.repayment_start_date:
 			frappe.throw(_("Repayment Start Date is mandatory for term loans"))
 
+		schedule_type_details = frappe.db.get_value(
+			"Loan Type", self.loan_type, ["repayment_schedule_type", "repayment_date_on"], as_dict=1
+		)
+
 		self.repayment_schedule = []
 		payment_date = self.repayment_start_date
 		balance_amount = self.loan_amount
-		while balance_amount > 0:
-			interest_amount = flt(balance_amount * flt(self.rate_of_interest) / (12 * 100))
-			principal_amount = self.monthly_repayment_amount - interest_amount
-			balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
-			if balance_amount < 0:
-				principal_amount += balance_amount
-				balance_amount = 0.0
 
-			total_payment = principal_amount + interest_amount
-			self.append(
-				"repayment_schedule",
-				{
-					"payment_date": payment_date,
-					"principal_amount": principal_amount,
-					"interest_amount": interest_amount,
-					"total_payment": total_payment,
-					"balance_loan_amount": balance_amount,
-				},
+		while balance_amount > 0:
+			interest_amount, principal_amount, balance_amount, total_payment = self.get_amounts(
+				payment_date,
+				balance_amount,
+				schedule_type_details.repayment_schedule_type,
+				schedule_type_details.repayment_date_on,
 			)
-			next_payment_date = add_single_month(payment_date)
-			payment_date = next_payment_date
+
+			if schedule_type_details.repayment_schedule_type == "Pro-rated calendar months":
+				next_payment_date = get_last_day(payment_date)
+				if schedule_type_details.repayment_date_on == "Start of the next month":
+					next_payment_date = add_days(next_payment_date, 1)
+
+				payment_date = next_payment_date
+
+			self.add_repayment_schedule_row(
+				payment_date, principal_amount, interest_amount, total_payment, balance_amount
+			)
+
+			if (
+				schedule_type_details.repayment_schedule_type == "Monthly as per repayment start date"
+				or schedule_type_details.repayment_date_on == "End of the current month"
+			):
+				next_payment_date = add_single_month(payment_date)
+				payment_date = next_payment_date
+
+	def get_amounts(self, payment_date, balance_amount, schedule_type, repayment_date_on):
+		if schedule_type == "Monthly as per repayment start date":
+			days = 1
+			months = 12
+		else:
+			expected_payment_date = get_last_day(payment_date)
+			if repayment_date_on == "Start of the next month":
+				expected_payment_date = add_days(expected_payment_date, 1)
+
+			if expected_payment_date == payment_date:
+				# using 30 days for calculating interest for all full months
+				days = 30
+				months = 365
+			else:
+				days = date_diff(get_last_day(payment_date), payment_date)
+				months = 365
+
+		interest_amount = flt(balance_amount * flt(self.rate_of_interest) * days / (months * 100))
+		principal_amount = self.monthly_repayment_amount - interest_amount
+		balance_amount = flt(balance_amount + interest_amount - self.monthly_repayment_amount)
+		if balance_amount < 0:
+			principal_amount += balance_amount
+			balance_amount = 0.0
+
+		total_payment = principal_amount + interest_amount
+
+		return interest_amount, principal_amount, balance_amount, total_payment
+
+	def add_repayment_schedule_row(
+		self, payment_date, principal_amount, interest_amount, total_payment, balance_loan_amount
+	):
+		self.append(
+			"repayment_schedule",
+			{
+				"payment_date": payment_date,
+				"principal_amount": principal_amount,
+				"interest_amount": interest_amount,
+				"total_payment": total_payment,
+				"balance_loan_amount": balance_loan_amount,
+			},
+		)
 
 	def set_repayment_period(self):
 		if self.repayment_method == "Repay Fixed Amount per Period":
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index da05c8e..388e65d 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -4,7 +4,16 @@
 import unittest
 
 import frappe
-from frappe.utils import add_days, add_months, add_to_date, date_diff, flt, get_datetime, nowdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	add_to_date,
+	date_diff,
+	flt,
+	format_date,
+	get_datetime,
+	nowdate,
+)
 
 from erpnext.loan_management.doctype.loan.loan import (
 	make_loan_write_off,
@@ -47,6 +56,51 @@
 			loan_account="Loan Account - _TC",
 			interest_income_account="Interest Income Account - _TC",
 			penalty_income_account="Penalty Income Account - _TC",
+			repayment_schedule_type="Monthly as per repayment start date",
+		)
+
+		create_loan_type(
+			"Term Loan Type 1",
+			12000,
+			7.5,
+			is_term_loan=1,
+			mode_of_payment="Cash",
+			disbursement_account="Disbursement Account - _TC",
+			payment_account="Payment Account - _TC",
+			loan_account="Loan Account - _TC",
+			interest_income_account="Interest Income Account - _TC",
+			penalty_income_account="Penalty Income Account - _TC",
+			repayment_schedule_type="Monthly as per repayment start date",
+		)
+
+		create_loan_type(
+			"Term Loan Type 2",
+			12000,
+			7.5,
+			is_term_loan=1,
+			mode_of_payment="Cash",
+			disbursement_account="Disbursement Account - _TC",
+			payment_account="Payment Account - _TC",
+			loan_account="Loan Account - _TC",
+			interest_income_account="Interest Income Account - _TC",
+			penalty_income_account="Penalty Income Account - _TC",
+			repayment_schedule_type="Pro-rated calendar months",
+			repayment_date_on="Start of the next month",
+		)
+
+		create_loan_type(
+			"Term Loan Type 3",
+			12000,
+			7.5,
+			is_term_loan=1,
+			mode_of_payment="Cash",
+			disbursement_account="Disbursement Account - _TC",
+			payment_account="Payment Account - _TC",
+			loan_account="Loan Account - _TC",
+			interest_income_account="Interest Income Account - _TC",
+			penalty_income_account="Penalty Income Account - _TC",
+			repayment_schedule_type="Pro-rated calendar months",
+			repayment_date_on="End of the current month",
 		)
 
 		create_loan_type(
@@ -62,6 +116,7 @@
 			"Loan Account - _TC",
 			"Interest Income Account - _TC",
 			"Penalty Income Account - _TC",
+			repayment_schedule_type="Monthly as per repayment start date",
 		)
 
 		create_loan_type(
@@ -902,6 +957,69 @@
 		amounts = calculate_amounts(loan.name, add_days(last_date, 5))
 		self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0)
 
+	def test_term_loan_schedule_types(self):
+		loan = create_loan(
+			self.applicant1,
+			"Term Loan Type 1",
+			12000,
+			"Repay Over Number of Periods",
+			12,
+			repayment_start_date="2022-10-17",
+		)
+
+		# Check for first, second and last installment date
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "17-10-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "17-11-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "17-09-2023"
+		)
+
+		loan.loan_type = "Term Loan Type 2"
+		loan.save()
+
+		# Check for first, second and last installment date
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "01-11-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "01-12-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "01-10-2023"
+		)
+
+		loan.loan_type = "Term Loan Type 3"
+		loan.save()
+
+		# Check for first, second and last installment date
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
+		)
+
+		loan.repayment_method = "Repay Fixed Amount per Period"
+		loan.monthly_repayment_amount = 1042
+		loan.save()
+
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
+		)
+		self.assertEqual(
+			format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
+		)
+
 
 def create_loan_scenario_for_penalty(doc):
 	pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
@@ -1033,6 +1151,8 @@
 	penalty_income_account=None,
 	repayment_method=None,
 	repayment_periods=None,
+	repayment_schedule_type=None,
+	repayment_date_on=None,
 ):
 
 	if not frappe.db.exists("Loan Type", loan_name):
@@ -1042,6 +1162,7 @@
 				"company": "_Test Company",
 				"loan_name": loan_name,
 				"is_term_loan": is_term_loan,
+				"repayment_schedule_type": "Monthly as per repayment start date",
 				"maximum_loan_amount": maximum_loan_amount,
 				"rate_of_interest": rate_of_interest,
 				"penalty_interest_rate": penalty_interest_rate,
@@ -1056,8 +1177,14 @@
 				"repayment_periods": repayment_periods,
 				"write_off_amount": 100,
 			}
-		).insert()
+		)
 
+		if loan_type.is_term_loan:
+			loan_type.repayment_schedule_type = repayment_schedule_type
+			if loan_type.repayment_schedule_type != "Monthly as per repayment start date":
+				loan_type.repayment_date_on = repayment_date_on
+
+		loan_type.insert()
 		loan_type.submit()
 
 
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index 00337e4..5cc9464 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -16,6 +16,8 @@
   "company",
   "is_term_loan",
   "disabled",
+  "repayment_schedule_type",
+  "repayment_date_on",
   "description",
   "account_details_section",
   "mode_of_payment",
@@ -157,12 +159,30 @@
    "label": "Disbursement Account",
    "options": "Account",
    "reqd": 1
+  },
+  {
+   "depends_on": "is_term_loan",
+   "description": "The schedule type that will be used for generating the term loan schedules (will affect the payment date and monthly repayment amount)",
+   "fieldname": "repayment_schedule_type",
+   "fieldtype": "Select",
+   "label": "Repayment Schedule Type",
+   "mandatory_depends_on": "is_term_loan",
+   "options": "\nMonthly as per repayment start date\nPro-rated calendar months"
+  },
+  {
+   "depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"",
+   "description": "Select whether the repayment date should be the end of the current month or start of the upcoming month",
+   "fieldname": "repayment_date_on",
+   "fieldtype": "Select",
+   "label": "Repayment Date On",
+   "mandatory_depends_on": "eval:doc.repayment_schedule_type == \"Pro-rated calendar months\"",
+   "options": "\nStart of the next month\nEnd of the current month"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-01-25 16:23:57.009349",
+ "modified": "2022-10-22 17:43:03.954201",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index ff84991..580838e 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -385,6 +385,7 @@
 		if self.docstatus == 2:
 			return
 
+		self.flags.cost_updated = False
 		existing_bom_cost = self.total_cost
 
 		if self.docstatus == 1:
@@ -407,7 +408,11 @@
 				frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
 
 		if not from_child_bom:
-			frappe.msgprint(_("Cost Updated"), alert=True)
+			msg = "Cost Updated"
+			if not self.flags.cost_updated:
+				msg = "No changes in cost found"
+
+			frappe.msgprint(_(msg), alert=True)
 
 	def update_parent_cost(self):
 		if self.total_cost:
@@ -593,11 +598,16 @@
 			# not via doc event, table is not regenerated and needs updation
 			self.calculate_exploded_cost()
 
+		old_cost = self.total_cost
+
 		self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
 		self.base_total_cost = (
 			self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
 		)
 
+		if self.total_cost != old_cost:
+			self.flags.cost_updated = True
+
 	def calculate_op_cost(self, update_hour_rate=False):
 		"""Update workstation rate and calculates totals"""
 		self.operating_cost = 0
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 27f3cc9..e34ac12 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -9,7 +9,10 @@
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import cstr, flt
 
-from erpnext.controllers.tests.test_subcontracting_controller import set_backflush_based_on
+from erpnext.controllers.tests.test_subcontracting_controller import (
+	make_stock_in_entry,
+	set_backflush_based_on,
+)
 from erpnext.manufacturing.doctype.bom.bom import BOMRecursionError, item_query, make_variant_bom
 from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
 	update_cost_in_all_boms_in_test,
@@ -639,6 +642,28 @@
 		bom.submit()
 		self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
 
+	def test_bom_cost_update_flag(self):
+		rm_item = make_item(
+			properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
+		).name
+		fg_item = make_item(properties={"is_stock_item": 1}).name
+
+		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+		bom = make_bom(item=fg_item, raw_materials=[rm_item])
+
+		create_stock_reconciliation(
+			item_code=rm_item, warehouse="_Test Warehouse - _TC", qty=100, rate=600
+		)
+
+		bom.load_from_db()
+		bom.update_cost()
+		self.assertTrue(bom.flags.cost_updated)
+
+		bom.load_from_db()
+		bom.update_cost()
+		self.assertFalse(bom.flags.cost_updated)
+
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index ed45106..fb94e8a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -133,7 +133,7 @@
 				(%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0}
 			)
 			and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1}
-			order by jctl.to_time desc limit 1""".format(
+			order by jctl.to_time desc""".format(
 				extra_cond, validate_overlap_for
 			),
 			{
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index ac71141..4d2dab7 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -136,6 +136,45 @@
 		)
 		self.assertRaises(OverlapError, jc2.save)
 
+	def test_job_card_overlap_with_capacity(self):
+		wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
+
+		workstation = make_workstation(workstation_name=random_string(5)).name
+		frappe.db.set_value("Workstation", workstation, "production_capacity", 1)
+
+		jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
+		jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
+
+		jc1.workstation = workstation
+		jc1.append(
+			"time_logs",
+			{"from_time": "2021-01-01 00:00:00", "to_time": "2021-01-01 08:00:00", "completed_qty": 1},
+		)
+		jc1.save()
+
+		jc2.workstation = workstation
+
+		# add a new entry in same time slice
+		jc2.append(
+			"time_logs",
+			{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
+		)
+		self.assertRaises(OverlapError, jc2.save)
+
+		frappe.db.set_value("Workstation", workstation, "production_capacity", 2)
+		jc2.load_from_db()
+
+		jc2.workstation = workstation
+
+		# add a new entry in same time slice
+		jc2.append(
+			"time_logs",
+			{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 1},
+		)
+
+		jc2.save()
+		self.assertTrue(jc2.name)
+
 	def test_job_card_multiple_materials_transfer(self):
 		"Test transferring RMs separately against Job Card with multiple RMs."
 		self.transfer_material_against = "Job Card"
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4bb4dcc..000ee07 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -27,6 +27,7 @@
 from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
 from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
+from erpnext.stock.get_item_details import get_conversion_factor
 from erpnext.utilities.transaction_base import validate_uom_is_integer
 
 
@@ -648,13 +649,23 @@
 			else:
 				material_request = material_request_map[key]
 
+			conversion_factor = 1.0
+			if (
+				material_request_type == "Purchase"
+				and item_doc.purchase_uom
+				and item_doc.purchase_uom != item_doc.stock_uom
+			):
+				conversion_factor = (
+					get_conversion_factor(item_doc.name, item_doc.purchase_uom).get("conversion_factor") or 1.0
+				)
+
 			# add item
 			material_request.append(
 				"items",
 				{
 					"item_code": item.item_code,
 					"from_warehouse": item.from_warehouse,
-					"qty": item.quantity,
+					"qty": item.quantity / conversion_factor,
 					"schedule_date": schedule_date,
 					"warehouse": item.warehouse,
 					"sales_order": item.sales_order,
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 60e6398..c4ab0f8 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -806,6 +806,35 @@
 		self.assertEqual(pln.status, "Completed")
 		self.assertEqual(pln.po_items[0].produced_qty, 5)
 
+	def test_material_request_item_for_purchase_uom(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+		bom_item = make_item(
+			properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
+		).name
+
+		if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
+			doc = frappe.get_doc("Item", bom_item)
+			doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
+			doc.save()
+
+		make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(
+			item_code=fg_item, planned_qty=10, ignore_existing_ordered_qty=1, stock_uom="_Test UOM 1"
+		)
+
+		pln.make_material_request()
+		for row in frappe.get_all(
+			"Material Request Item",
+			filters={"production_plan": pln.name},
+			fields=["item_code", "uom", "qty"],
+		):
+			self.assertEqual(row.item_code, bom_item)
+			self.assertEqual(row.uom, "Nos")
+			self.assertEqual(row.qty, 1)
+
 
 def create_production_plan(**args):
 	"""
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index fb937f6..1605c52 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -315,5 +315,6 @@
 erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
 erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
 erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
+erpnext.patches.v13_0.update_schedule_type_in_loans
 erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
 erpnext.patches.v14_0.update_tds_fields
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_schedule_type_in_loans.py b/erpnext/patches/v13_0/update_schedule_type_in_loans.py
new file mode 100644
index 0000000..e5b5f64
--- /dev/null
+++ b/erpnext/patches/v13_0/update_schedule_type_in_loans.py
@@ -0,0 +1,14 @@
+import frappe
+
+
+def execute():
+	loan = frappe.qb.DocType("Loan")
+	loan_type = frappe.qb.DocType("Loan Type")
+
+	frappe.qb.update(loan_type).set(
+		loan_type.repayment_schedule_type, "Monthly as per repayment start date"
+	).where(loan_type.is_term_loan == 1).run()
+
+	frappe.qb.update(loan).set(
+		loan.repayment_schedule_type, "Monthly as per repayment start date"
+	).where(loan.is_term_loan == 1).run()
diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js
index 1a309ba..b0082bd 100644
--- a/erpnext/public/js/financial_statements.js
+++ b/erpnext/public/js/financial_statements.js
@@ -28,7 +28,7 @@
 	},
 	"open_general_ledger": function(data) {
 		if (!data.account) return;
-		var project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; })
+		let project = $.grep(frappe.query_report.filters, function(e){ return e.df.fieldname == 'project'; });
 
 		frappe.route_options = {
 			"account": data.account,
@@ -37,7 +37,16 @@
 			"to_date": data.to_date || data.year_end_date,
 			"project": (project && project.length > 0) ? project[0].$input.val() : ""
 		};
-		frappe.set_route("query-report", "General Ledger");
+
+		let report = "General Ledger";
+
+		if (["Payable", "Receivable"].includes(data.account_type)) {
+			report = data.account_type == "Payable" ? "Accounts Payable" : "Accounts Receivable";
+			frappe.route_options["party_account"] = data.account;
+			frappe.route_options["report_date"] = data.year_end_date;
+		}
+
+		frappe.set_route("query-report", report);
 	},
 	"tree": true,
 	"name_field": "account",
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 58594b0..644adff 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -242,20 +242,29 @@
 	});
 };
 
-erpnext.utils.get_contact_details = function(frm) {
+erpnext.utils.get_contact_details = function (frm) {
 	if (frm.updating_party_details) return;
 
 	if (frm.doc["contact_person"]) {
 		frappe.call({
 			method: "frappe.contacts.doctype.contact.contact.get_contact_details",
-			args: {contact: frm.doc.contact_person },
-			callback: function(r) {
-				if (r.message)
-					frm.set_value(r.message);
-			}
-		})
+			args: { contact: frm.doc.contact_person },
+			callback: function (r) {
+				if (r.message) frm.set_value(r.message);
+			},
+		});
+	} else {
+		frm.set_value({
+			contact_person: "",
+			contact_display: "",
+			contact_email: "",
+			contact_mobile: "",
+			contact_phone: "",
+			contact_designation: "",
+			contact_department: "",
+		});
 	}
-}
+};
 
 erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
 	if (!value) {
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 7dc3fab..691adcc 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -3,6 +3,7 @@
 
 
 import frappe
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.test_runner import make_test_records
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import flt
@@ -341,6 +342,33 @@
 		due_date = get_due_date("2017-01-22", "Customer", "_Test Customer")
 		self.assertEqual(due_date, "2017-01-22")
 
+	def test_serach_fields_for_customer(self):
+		from erpnext.controllers.queries import customer_query
+
+		make_property_setter(
+			"Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype"
+		)
+
+		data = customer_query(
+			"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
+		)
+
+		self.assertEqual(data[0].name, "_Test Customer")
+		self.assertEqual(data[0].customer_group, "_Test Customer Group")
+		self.assertTrue("territory" not in data[0])
+
+		make_property_setter(
+			"Customer", None, "search_fields", "customer_group, territory", "Data", for_doctype="Doctype"
+		)
+		data = customer_query(
+			"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
+		)
+
+		self.assertEqual(data[0].name, "_Test Customer")
+		self.assertEqual(data[0].customer_group, "_Test Customer Group")
+		self.assertEqual(data[0].territory, "_Test Territory")
+		self.assertTrue("territory" in data[0])
+
 
 def get_customer_dict(customer_name):
 	return {
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 70ae085..6b42e4d 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -84,11 +84,12 @@
 			}
 		}
 
-		if(doc.docstatus == 1 && !(['Lost', 'Ordered']).includes(doc.status)) {
-			if(!doc.valid_till || frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
-				cur_frm.add_custom_button(__('Sales Order'),
-					cur_frm.cscript['Make Sales Order'], __('Create'));
-			}
+		if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
+			this.frm.add_custom_button(
+				__("Sales Order"),
+				this.frm.cscript["Make Sales Order"],
+				__("Create")
+			);
 
 			if(doc.status!=="Ordered") {
 				this.frm.add_custom_button(__('Set as Lost'), () => {
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 2cf836f..ea0b25f 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -272,6 +272,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "discount_and_margin",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -842,7 +843,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-09-06 13:24:18.065312",
+ "modified": "2022-10-26 16:05:02.712705",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 36d5a6c..9dd28dc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -842,6 +842,9 @@
 			update_address(
 				target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
 			)
+			update_address(
+				target_doc, "billing_address", "billing_address_display", source_doc.customer_address
+			)
 
 			update_taxes(
 				target_doc,
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 0a5cbab..77c3253 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -261,6 +261,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "discount_and_margin",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -814,7 +815,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-10-12 03:36:05.344847",
+ "modified": "2022-10-26 16:05:17.720768",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
index 06ba936..b3ae7b5 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
@@ -12,13 +12,17 @@
 			"Purchase Receipt": "return_against",
 		},
 		"internal_links": {
+			"Material Request": ["items", "material_request"],
 			"Purchase Order": ["items", "purchase_order"],
 			"Project": ["items", "project"],
 			"Quality Inspection": ["items", "quality_inspection"],
 		},
 		"transactions": [
 			{"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]},
-			{"label": _("Reference"), "items": ["Purchase Order", "Quality Inspection", "Project"]},
+			{
+				"label": _("Reference"),
+				"items": ["Material Request", "Purchase Order", "Quality Inspection", "Project"],
+			},
 			{"label": _("Returns"), "items": ["Purchase Receipt"]},
 			{"label": _("Subscription"), "items": ["Auto Repeat"]},
 		],
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 772736e..474ee92 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -919,6 +919,7 @@
   },
   {
    "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
    "fieldname": "discount_and_margin_section",
    "fieldtype": "Section Break",
    "label": "Discount and Margin"
@@ -1000,7 +1001,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-10-12 03:37:59.516609",
+ "modified": "2022-10-26 16:06:02.524435",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 9fb3be5..b8c5187 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -13,6 +13,8 @@
 import erpnext
 from erpnext.stock.valuation import FIFOValuation, LIFOValuation
 
+BarcodeScanResult = Dict[str, Optional[str]]
+
 
 class InvalidWarehouseCompany(frappe.ValidationError):
 	pass
@@ -552,7 +554,16 @@
 
 
 @frappe.whitelist()
-def scan_barcode(search_value: str) -> Dict[str, Optional[str]]:
+def scan_barcode(search_value: str) -> BarcodeScanResult:
+	def set_cache(data: BarcodeScanResult):
+		frappe.cache().set_value(f"erpnext:barcode_scan:{search_value}", data, expires_in_sec=120)
+
+	def get_cache() -> Optional[BarcodeScanResult]:
+		if data := frappe.cache().get_value(f"erpnext:barcode_scan:{search_value}"):
+			return data
+
+	if scan_data := get_cache():
+		return scan_data
 
 	# search barcode no
 	barcode_data = frappe.db.get_value(
@@ -562,7 +573,9 @@
 		as_dict=True,
 	)
 	if barcode_data:
-		return _update_item_info(barcode_data)
+		_update_item_info(barcode_data)
+		set_cache(barcode_data)
+		return barcode_data
 
 	# search serial no
 	serial_no_data = frappe.db.get_value(
@@ -572,7 +585,9 @@
 		as_dict=True,
 	)
 	if serial_no_data:
-		return _update_item_info(serial_no_data)
+		_update_item_info(serial_no_data)
+		set_cache(serial_no_data)
+		return serial_no_data
 
 	# search batch no
 	batch_no_data = frappe.db.get_value(
@@ -582,7 +597,9 @@
 		as_dict=True,
 	)
 	if batch_no_data:
-		return _update_item_info(batch_no_data)
+		_update_item_info(batch_no_data)
+		set_cache(batch_no_data)
+		return batch_no_data
 
 	return {}