Merge pull request #31395 from nihantra/bank_clear_summ

fix: filter set in Bank Clearance Summary
diff --git a/erpnext/accounts/README.md b/erpnext/accounts/README.md
index da1f201..15f7039 100644
--- a/erpnext/accounts/README.md
+++ b/erpnext/accounts/README.md
@@ -10,4 +10,42 @@
 - Sales Invoice (Itemised)
 - Purchase Invoice (Itemised)
 
-All accounting entries are stored in the `General Ledger`
\ No newline at end of file
+All accounting entries are stored in the `General Ledger`
+
+## Payment Ledger
+Transactions on Receivable and Payable Account types will also be stored in `Payment Ledger`. This is so that payment reconciliation process only requires update on this ledger.
+
+### Key Fields
+| Field                | Description                      |
+|----------------------|----------------------------------|
+| `account_type`       | Receivable/Payable               |
+| `account`            | Accounting head                  |
+| `party`              | Party Name                       |
+| `voucher_no`         | Voucher No                       |
+| `against_voucher_no` | Linked voucher(secondary effect) |
+| `amount`             | can be +ve/-ve                   |
+
+### Design
+`debit` and `credit` have been replaced with `account_type` and `amount`. `against_voucher_no` is populated for all entries. So, outstanding amount can be calculated by summing up amount only using `against_voucher_no`.
+
+Ex:
+1. Consider an invoice for ₹100 and a partial payment of ₹80 against that invoice. Payment Ledger will have following entries.
+
+| voucher_no | against_voucher_no | amount |
+|------------|--------------------|--------|
+| SINV-01    | SINV-01            | 100    |
+| PAY-01     | SINV-01            | -80    |
+
+
+2. Reconcile a Credit Note against an invoice using a Journal Entry
+
+An invoice for ₹100 partially reconciled against a credit of ₹70 using a Journal Entry. Payment Ledger will have the following entries.
+
+| voucher_no | against_voucher_no | amount |
+|------------|--------------------|--------|
+| SINV-01    | SINV-01            | 100    |
+|            |                    |        |
+| CR-NOTE-01 | CR-NOTE-01         | -70    |
+|            |                    |        |
+| JE-01      | CR-NOTE-01         | +70    |
+| JE-01      | SINV-01            | -70    |
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index e5fa57d..9f71656 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -58,16 +58,20 @@
 			validate_balance_type(self.account, adv_adj)
 			validate_frozen_account(self.account, adv_adj)
 
-			# Update outstanding amt on against voucher
-			if (
-				self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
-				and self.against_voucher
-				and self.flags.update_outstanding == "Yes"
-				and not frappe.flags.is_reverse_depr_entry
-			):
-				update_outstanding_amt(
-					self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
-				)
+			if frappe.db.get_value("Account", self.account, "account_type") not in [
+				"Receivable",
+				"Payable",
+			]:
+				# Update outstanding amt on against voucher
+				if (
+					self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
+					and self.against_voucher
+					and self.flags.update_outstanding == "Yes"
+					and not frappe.flags.is_reverse_depr_entry
+				):
+					update_outstanding_amt(
+						self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
+					)
 
 	def check_mandatory(self):
 		mandatory = ["account", "voucher_type", "voucher_no", "company"]
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 2c16ca3..787efd2 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -800,9 +800,7 @@
 
 		self.total_amount_in_words = money_in_words(amt, currency)
 
-	def make_gl_entries(self, cancel=0, adv_adj=0):
-		from erpnext.accounts.general_ledger import make_gl_entries
-
+	def build_gl_map(self):
 		gl_map = []
 		for d in self.get("accounts"):
 			if d.debit or d.credit:
@@ -838,7 +836,12 @@
 						item=d,
 					)
 				)
+		return gl_map
 
+	def make_gl_entries(self, cancel=0, adv_adj=0):
+		from erpnext.accounts.general_ledger import make_gl_entries
+
+		gl_map = self.build_gl_map()
 		if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
 			update_outstanding = "No"
 		else:
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index f7a57bb..d8af9db 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -6,7 +6,7 @@
 from functools import reduce
 
 import frappe
-from frappe import ValidationError, _, scrub, throw
+from frappe import ValidationError, _, qb, scrub, throw
 from frappe.utils import cint, comma_or, flt, getdate, nowdate
 
 import erpnext
@@ -785,7 +785,7 @@
 
 		self.set("remarks", "\n".join(remarks))
 
-	def make_gl_entries(self, cancel=0, adv_adj=0):
+	def build_gl_map(self):
 		if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
 			self.setup_party_account_field()
 
@@ -794,7 +794,10 @@
 		self.add_bank_gl_entries(gl_entries)
 		self.add_deductions_gl_entries(gl_entries)
 		self.add_tax_gl_entries(gl_entries)
+		return gl_entries
 
+	def make_gl_entries(self, cancel=0, adv_adj=0):
+		gl_entries = self.build_gl_map()
 		gl_entries = process_gl_map(gl_entries)
 		make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
 
@@ -1195,6 +1198,9 @@
 	if args.get("party_type") == "Member":
 		return
 
+	ple = qb.DocType("Payment Ledger Entry")
+	common_filter = []
+
 	# confirm that Supplier is not blocked
 	if args.get("party_type") == "Supplier":
 		supplier_status = get_supplier_block_status(args["party"])
@@ -1216,10 +1222,13 @@
 		condition = " and voucher_type={0} and voucher_no={1}".format(
 			frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
 		)
+		common_filter.append(ple.voucher_type == args["voucher_type"])
+		common_filter.append(ple.voucher_no == args["voucher_no"])
 
 	# Add cost center condition
 	if args.get("cost_center"):
 		condition += " and cost_center='%s'" % args.get("cost_center")
+		common_filter.append(ple.cost_center == args.get("cost_center"))
 
 	date_fields_dict = {
 		"posting_date": ["from_posting_date", "to_posting_date"],
@@ -1231,16 +1240,19 @@
 			condition += " and {0} between '{1}' and '{2}'".format(
 				fieldname, args.get(date_fields[0]), args.get(date_fields[1])
 			)
+			common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
 
 	if args.get("company"):
 		condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
+		common_filter.append(ple.company == args.get("company"))
 
 	outstanding_invoices = get_outstanding_invoices(
 		args.get("party_type"),
 		args.get("party"),
 		args.get("party_account"),
-		filters=args,
-		condition=condition,
+		common_filter=common_filter,
+		min_outstanding=args.get("outstanding_amt_greater_than"),
+		max_outstanding=args.get("outstanding_amt_less_than"),
 	)
 
 	outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
index 43e19f4..52df923 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
@@ -6,6 +6,19 @@
 from frappe import _
 from frappe.model.document import Document
 
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+	get_checks_for_pl_and_bs_accounts,
+)
+from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
+	get_dimension_filter_map,
+)
+from erpnext.accounts.doctype.gl_entry.gl_entry import (
+	validate_balance_type,
+	validate_frozen_account,
+)
+from erpnext.accounts.utils import update_voucher_outstanding
+from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
+
 
 class PaymentLedgerEntry(Document):
 	def validate_account(self):
@@ -18,5 +31,119 @@
 		if not valid_account:
 			frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
 
+	def validate_account_details(self):
+		"""Account must be ledger, active and not freezed"""
+
+		ret = frappe.db.sql(
+			"""select is_group, docstatus, company
+			from tabAccount where name=%s""",
+			self.account,
+			as_dict=1,
+		)[0]
+
+		if ret.is_group == 1:
+			frappe.throw(
+				_(
+					"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
+				).format(self.voucher_type, self.voucher_no, self.account)
+			)
+
+		if ret.docstatus == 2:
+			frappe.throw(
+				_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
+			)
+
+		if ret.company != self.company:
+			frappe.throw(
+				_("{0} {1}: Account {2} does not belong to Company {3}").format(
+					self.voucher_type, self.voucher_no, self.account, self.company
+				)
+			)
+
+	def validate_allowed_dimensions(self):
+		dimension_filter_map = get_dimension_filter_map()
+		for key, value in dimension_filter_map.items():
+			dimension = key[0]
+			account = key[1]
+
+			if self.account == account:
+				if value["is_mandatory"] and not self.get(dimension):
+					frappe.throw(
+						_("{0} is mandatory for account {1}").format(
+							frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
+						),
+						MandatoryAccountDimensionError,
+					)
+
+				if value["allow_or_restrict"] == "Allow":
+					if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
+						frappe.throw(
+							_("Invalid value {0} for {1} against account {2}").format(
+								frappe.bold(self.get(dimension)),
+								frappe.bold(frappe.unscrub(dimension)),
+								frappe.bold(self.account),
+							),
+							InvalidAccountDimensionError,
+						)
+				else:
+					if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
+						frappe.throw(
+							_("Invalid value {0} for {1} against account {2}").format(
+								frappe.bold(self.get(dimension)),
+								frappe.bold(frappe.unscrub(dimension)),
+								frappe.bold(self.account),
+							),
+							InvalidAccountDimensionError,
+						)
+
+	def validate_dimensions_for_pl_and_bs(self):
+		account_type = frappe.db.get_value("Account", self.account, "report_type")
+
+		for dimension in get_checks_for_pl_and_bs_accounts():
+			if (
+				account_type == "Profit and Loss"
+				and self.company == dimension.company
+				and dimension.mandatory_for_pl
+				and not dimension.disabled
+			):
+				if not self.get(dimension.fieldname):
+					frappe.throw(
+						_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
+							dimension.label, self.account
+						)
+					)
+
+			if (
+				account_type == "Balance Sheet"
+				and self.company == dimension.company
+				and dimension.mandatory_for_bs
+				and not dimension.disabled
+			):
+				if not self.get(dimension.fieldname):
+					frappe.throw(
+						_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
+							dimension.label, self.account
+						)
+					)
+
 	def validate(self):
 		self.validate_account()
+
+	def on_update(self):
+		adv_adj = self.flags.adv_adj
+		if not self.flags.from_repost:
+			self.validate_account_details()
+			self.validate_dimensions_for_pl_and_bs()
+			self.validate_allowed_dimensions()
+			validate_balance_type(self.account, adv_adj)
+			validate_frozen_account(self.account, adv_adj)
+
+		# update outstanding amount
+		if (
+			self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
+			and self.flags.update_outstanding == "Yes"
+			and not frappe.flags.is_reverse_depr_entry
+		):
+			update_voucher_outstanding(
+				self.against_voucher_type, self.against_voucher_no, self.account, self.party_type, self.party
+			)
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index e5b942f..5b2b526 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -3,16 +3,26 @@
 
 
 import frappe
-from frappe import _, msgprint
+from frappe import _, msgprint, qb
 from frappe.model.document import Document
+from frappe.query_builder.custom import ConstantColumn
+from frappe.query_builder.functions import IfNull
 from frappe.utils import flt, getdate, nowdate, today
 
 import erpnext
-from erpnext.accounts.utils import get_outstanding_invoices, reconcile_against_document
+from erpnext.accounts.utils import (
+	QueryPaymentLedger,
+	get_outstanding_invoices,
+	reconcile_against_document,
+)
 from erpnext.controllers.accounts_controller import get_advance_payment_entries
 
 
 class PaymentReconciliation(Document):
+	def __init__(self, *args, **kwargs):
+		super(PaymentReconciliation, self).__init__(*args, **kwargs)
+		self.common_filter_conditions = []
+
 	@frappe.whitelist()
 	def get_unreconciled_entries(self):
 		self.get_nonreconciled_payment_entries()
@@ -108,54 +118,58 @@
 		return list(journal_entries)
 
 	def get_dr_or_cr_notes(self):
-		condition = self.get_conditions(get_return_invoices=True)
-		dr_or_cr = (
-			"credit_in_account_currency"
-			if erpnext.get_party_account_type(self.party_type) == "Receivable"
-			else "debit_in_account_currency"
-		)
 
-		reconciled_dr_or_cr = (
-			"debit_in_account_currency"
-			if dr_or_cr == "credit_in_account_currency"
-			else "credit_in_account_currency"
-		)
+		self.build_qb_filter_conditions(get_return_invoices=True)
 
+		ple = qb.DocType("Payment Ledger Entry")
 		voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
 
-		return frappe.db.sql(
-			""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
-				(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
-				account_currency as currency
-			FROM `tab{doc}` doc, `tabGL Entry` gl
-			WHERE
-				(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
-				and doc.{party_type_field} = %(party)s
-				and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
-				and gl.against_voucher_type = %(voucher_type)s
-				and doc.docstatus = 1 and gl.party = %(party)s
-				and gl.party_type = %(party_type)s and gl.account = %(account)s
-				and gl.is_cancelled = 0 {condition}
-			GROUP BY doc.name
-			Having
-				amount > 0
-			ORDER BY doc.posting_date
-		""".format(
-				doc=voucher_type,
-				dr_or_cr=dr_or_cr,
-				reconciled_dr_or_cr=reconciled_dr_or_cr,
-				party_type_field=frappe.scrub(self.party_type),
-				condition=condition or "",
-			),
-			{
-				"party": self.party,
-				"party_type": self.party_type,
-				"voucher_type": voucher_type,
-				"account": self.receivable_payable_account,
-			},
-			as_dict=1,
+		if erpnext.get_party_account_type(self.party_type) == "Receivable":
+			self.common_filter_conditions.append(ple.account_type == "Receivable")
+		else:
+			self.common_filter_conditions.append(ple.account_type == "Payable")
+		self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
+
+		# get return invoices
+		doc = qb.DocType(voucher_type)
+		return_invoices = (
+			qb.from_(doc)
+			.select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
+			.where(
+				(doc.docstatus == 1)
+				& (doc[frappe.scrub(self.party_type)] == self.party)
+				& (doc.is_return == 1)
+				& (IfNull(doc.return_against, "") == "")
+			)
+			.run(as_dict=True)
 		)
 
+		outstanding_dr_or_cr = []
+		if return_invoices:
+			ple_query = QueryPaymentLedger()
+			return_outstanding = ple_query.get_voucher_outstandings(
+				vouchers=return_invoices,
+				common_filter=self.common_filter_conditions,
+				min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
+				max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
+				get_payments=True,
+			)
+
+			for inv in return_outstanding:
+				if inv.outstanding != 0:
+					outstanding_dr_or_cr.append(
+						frappe._dict(
+							{
+								"reference_type": inv.voucher_type,
+								"reference_name": inv.voucher_no,
+								"amount": -(inv.outstanding),
+								"posting_date": inv.posting_date,
+								"currency": inv.currency,
+							}
+						)
+					)
+		return outstanding_dr_or_cr
+
 	def add_payment_entries(self, non_reconciled_payments):
 		self.set("payments", [])
 
@@ -166,10 +180,15 @@
 	def get_invoice_entries(self):
 		# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
 
-		condition = self.get_conditions(get_invoices=True)
+		self.build_qb_filter_conditions(get_invoices=True)
 
 		non_reconciled_invoices = get_outstanding_invoices(
-			self.party_type, self.party, self.receivable_payable_account, condition=condition
+			self.party_type,
+			self.party,
+			self.receivable_payable_account,
+			common_filter=self.common_filter_conditions,
+			min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
+			max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
 		)
 
 		if self.invoice_limit:
@@ -329,89 +348,56 @@
 		if not invoices_to_reconcile:
 			frappe.throw(_("No records found in Allocation table"))
 
-	def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
-		condition = " and company = '{0}' ".format(self.company)
+	def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
+		self.common_filter_conditions.clear()
+		ple = qb.DocType("Payment Ledger Entry")
 
-		if self.get("cost_center") and (get_invoices or get_payments or get_return_invoices):
-			condition = " and cost_center = '{0}' ".format(self.cost_center)
+		self.common_filter_conditions.append(ple.company == self.company)
+
+		if self.get("cost_center") and (get_invoices or get_return_invoices):
+			self.common_filter_conditions.append(ple.cost_center == self.cost_center)
 
 		if get_invoices:
-			condition += (
-				" and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
-				if self.from_invoice_date
-				else ""
-			)
-			condition += (
-				" and posting_date <= {0}".format(frappe.db.escape(self.to_invoice_date))
-				if self.to_invoice_date
-				else ""
-			)
-			dr_or_cr = (
-				"debit_in_account_currency"
-				if erpnext.get_party_account_type(self.party_type) == "Receivable"
-				else "credit_in_account_currency"
-			)
-
-			if self.minimum_invoice_amount:
-				condition += " and {dr_or_cr} >= {amount}".format(
-					dr_or_cr=dr_or_cr, amount=flt(self.minimum_invoice_amount)
-				)
-			if self.maximum_invoice_amount:
-				condition += " and {dr_or_cr} <= {amount}".format(
-					dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
-				)
+			if self.from_invoice_date:
+				self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date))
+			if self.to_invoice_date:
+				self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date))
 
 		elif get_return_invoices:
-			condition = " and doc.company = '{0}' ".format(self.company)
-			condition += (
-				" and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
-				if self.from_payment_date
-				else ""
-			)
-			condition += (
-				" and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
-				if self.to_payment_date
-				else ""
-			)
-			dr_or_cr = (
-				"debit_in_account_currency"
-				if erpnext.get_party_account_type(self.party_type) == "Receivable"
-				else "credit_in_account_currency"
-			)
+			if self.from_payment_date:
+				self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date))
+			if self.to_payment_date:
+				self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date))
 
-			if self.minimum_invoice_amount:
-				condition += " and gl.{dr_or_cr} >= {amount}".format(
-					dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
-				)
-			if self.maximum_invoice_amount:
-				condition += " and gl.{dr_or_cr} <= {amount}".format(
-					dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
-				)
+	def get_conditions(self, get_payments=False):
+		condition = " and company = '{0}' ".format(self.company)
 
-		else:
-			condition += (
-				" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
-				if self.from_payment_date
-				else ""
-			)
-			condition += (
-				" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
-				if self.to_payment_date
-				else ""
-			)
+		if self.get("cost_center") and get_payments:
+			condition = " and cost_center = '{0}' ".format(self.cost_center)
 
-			if self.minimum_payment_amount:
-				condition += (
-					" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
-					if get_payments
-					else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
-				)
-			if self.maximum_payment_amount:
-				condition += (
-					" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
-					if get_payments
-					else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
-				)
+		condition += (
+			" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
+			if self.from_payment_date
+			else ""
+		)
+		condition += (
+			" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
+			if self.to_payment_date
+			else ""
+		)
+
+		if self.minimum_payment_amount:
+			condition += (
+				" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
+				if get_payments
+				else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
+			)
+		if self.maximum_payment_amount:
+			condition += (
+				" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
+				if get_payments
+				else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
+			)
 
 		return condition
 
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index d2374b7..575ac74 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -4,93 +4,453 @@
 import unittest
 
 import frappe
-from frappe.utils import add_days, getdate
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, nowdate
 
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.party import get_party_account
+from erpnext.stock.doctype.item.test_item import create_item
 
 
-class TestPaymentReconciliation(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
-		make_customer()
-		make_invoice_and_payment()
+class TestPaymentReconciliation(FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.create_item()
+		self.create_customer()
+		self.clear_old_entries()
 
-	def test_payment_reconciliation(self):
-		payment_reco = frappe.get_doc("Payment Reconciliation")
-		payment_reco.company = "_Test Company"
-		payment_reco.party_type = "Customer"
-		payment_reco.party = "_Test Payment Reco Customer"
-		payment_reco.receivable_payable_account = "Debtors - _TC"
-		payment_reco.from_invoice_date = add_days(getdate(), -1)
-		payment_reco.to_invoice_date = getdate()
-		payment_reco.from_payment_date = add_days(getdate(), -1)
-		payment_reco.to_payment_date = getdate()
-		payment_reco.maximum_invoice_amount = 1000
-		payment_reco.maximum_payment_amount = 1000
-		payment_reco.invoice_limit = 10
-		payment_reco.payment_limit = 10
-		payment_reco.bank_cash_account = "_Test Bank - _TC"
-		payment_reco.cost_center = "_Test Cost Center - _TC"
-		payment_reco.get_unreconciled_entries()
+	def tearDown(self):
+		frappe.db.rollback()
 
-		self.assertEqual(len(payment_reco.get("invoices")), 1)
-		self.assertEqual(len(payment_reco.get("payments")), 1)
+	def create_company(self):
+		company = None
+		if frappe.db.exists("Company", "_Test Payment Reconciliation"):
+			company = frappe.get_doc("Company", "_Test Payment Reconciliation")
+		else:
+			company = frappe.get_doc(
+				{
+					"doctype": "Company",
+					"company_name": "_Test Payment Reconciliation",
+					"country": "India",
+					"default_currency": "INR",
+					"create_chart_of_accounts_based_on": "Standard Template",
+					"chart_of_accounts": "Standard",
+				}
+			)
+			company = company.save()
 
-		payment_entry = payment_reco.get("payments")[0].reference_name
-		invoice = payment_reco.get("invoices")[0].invoice_number
+		self.company = company.name
+		self.cost_center = company.cost_center
+		self.warehouse = "All Warehouses - _PR"
+		self.income_account = "Sales - _PR"
+		self.expense_account = "Cost of Goods Sold - _PR"
+		self.debit_to = "Debtors - _PR"
+		self.creditors = "Creditors - _PR"
 
-		payment_reco.allocate_entries(
-			{
-				"payments": [payment_reco.get("payments")[0].as_dict()],
-				"invoices": [payment_reco.get("invoices")[0].as_dict()],
-			}
+		# create bank account
+		if frappe.db.exists("Account", "HDFC - _PR"):
+			self.bank = "HDFC - _PR"
+		else:
+			bank_acc = frappe.get_doc(
+				{
+					"doctype": "Account",
+					"account_name": "HDFC",
+					"parent_account": "Bank Accounts - _PR",
+					"company": self.company,
+				}
+			)
+			bank_acc.save()
+			self.bank = bank_acc.name
+
+	def create_item(self):
+		item = create_item(
+			item_code="_Test PR Item", is_stock_item=0, company=self.company, warehouse=self.warehouse
 		)
-		payment_reco.reconcile()
+		self.item = item if isinstance(item, str) else item.item_code
 
-		payment_entry_doc = frappe.get_doc("Payment Entry", payment_entry)
-		self.assertEqual(payment_entry_doc.get("references")[0].reference_name, invoice)
+	def create_customer(self):
+		if frappe.db.exists("Customer", "_Test PR Customer"):
+			self.customer = "_Test PR Customer"
+		else:
+			customer = frappe.new_doc("Customer")
+			customer.customer_name = "_Test PR Customer"
+			customer.type = "Individual"
+			customer.save()
+			self.customer = customer.name
 
+		if frappe.db.exists("Customer", "_Test PR Customer 2"):
+			self.customer2 = "_Test PR Customer 2"
+		else:
+			customer = frappe.new_doc("Customer")
+			customer.customer_name = "_Test PR Customer 2"
+			customer.type = "Individual"
+			customer.save()
+			self.customer2 = customer.name
 
-def make_customer():
-	if not frappe.db.get_value("Customer", "_Test Payment Reco Customer"):
-		frappe.get_doc(
-			{
-				"doctype": "Customer",
-				"customer_name": "_Test Payment Reco Customer",
-				"customer_type": "Individual",
-				"customer_group": "_Test Customer Group",
-				"territory": "_Test Territory",
-			}
-		).insert()
+	def create_sales_invoice(
+		self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
+	):
+		"""
+		Helper function to populate default values in sales invoice
+		"""
+		sinv = create_sales_invoice(
+			qty=qty,
+			rate=rate,
+			company=self.company,
+			customer=self.customer,
+			item_code=self.item,
+			item_name=self.item,
+			cost_center=self.cost_center,
+			warehouse=self.warehouse,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			update_stock=0,
+			currency="INR",
+			is_pos=0,
+			is_return=0,
+			return_against=None,
+			income_account=self.income_account,
+			expense_account=self.expense_account,
+			do_not_save=do_not_save,
+			do_not_submit=do_not_submit,
+		)
+		return sinv
 
+	def create_payment_entry(self, amount=100, posting_date=nowdate()):
+		"""
+		Helper function to populate default values in payment entry
+		"""
+		payment = create_payment_entry(
+			company=self.company,
+			payment_type="Receive",
+			party_type="Customer",
+			party=self.customer,
+			paid_from=self.debit_to,
+			paid_to=self.bank,
+			paid_amount=amount,
+		)
+		payment.posting_date = posting_date
+		return payment
 
-def make_invoice_and_payment():
-	si = create_sales_invoice(
-		customer="_Test Payment Reco Customer", qty=1, rate=690, do_not_save=True
-	)
-	si.cost_center = "_Test Cost Center - _TC"
-	si.save()
-	si.submit()
+	def clear_old_entries(self):
+		doctype_list = [
+			"GL Entry",
+			"Payment Ledger Entry",
+			"Sales Invoice",
+			"Purchase Invoice",
+			"Payment Entry",
+			"Journal Entry",
+		]
+		for doctype in doctype_list:
+			qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
 
-	pe = frappe.get_doc(
-		{
-			"doctype": "Payment Entry",
-			"payment_type": "Receive",
-			"party_type": "Customer",
-			"party": "_Test Payment Reco Customer",
-			"company": "_Test Company",
-			"paid_from_account_currency": "INR",
-			"paid_to_account_currency": "INR",
-			"source_exchange_rate": 1,
-			"target_exchange_rate": 1,
-			"reference_no": "1",
-			"reference_date": getdate(),
-			"received_amount": 690,
-			"paid_amount": 690,
-			"paid_from": "Debtors - _TC",
-			"paid_to": "_Test Bank - _TC",
-			"cost_center": "_Test Cost Center - _TC",
-		}
-	)
-	pe.insert()
-	pe.submit()
+	def create_payment_reconciliation(self):
+		pr = frappe.new_doc("Payment Reconciliation")
+		pr.company = self.company
+		pr.party_type = "Customer"
+		pr.party = self.customer
+		pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
+		pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
+		return pr
+
+	def create_journal_entry(
+		self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
+	):
+		je = frappe.new_doc("Journal Entry")
+		je.posting_date = posting_date or nowdate()
+		je.company = self.company
+		je.user_remark = "test"
+		if not cost_center:
+			cost_center = self.cost_center
+		je.set(
+			"accounts",
+			[
+				{
+					"account": acc1,
+					"cost_center": cost_center,
+					"debit_in_account_currency": amount if amount > 0 else 0,
+					"credit_in_account_currency": abs(amount) if amount < 0 else 0,
+				},
+				{
+					"account": acc2,
+					"cost_center": cost_center,
+					"credit_in_account_currency": amount if amount > 0 else 0,
+					"debit_in_account_currency": abs(amount) if amount < 0 else 0,
+				},
+			],
+		)
+		return je
+
+	def test_filter_min_max(self):
+		# check filter condition minimum and maximum amount
+		self.create_sales_invoice(qty=1, rate=300)
+		self.create_sales_invoice(qty=1, rate=400)
+		self.create_sales_invoice(qty=1, rate=500)
+		self.create_payment_entry(amount=300).save().submit()
+		self.create_payment_entry(amount=400).save().submit()
+		self.create_payment_entry(amount=500).save().submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.minimum_invoice_amount = 400
+		pr.maximum_invoice_amount = 500
+		pr.minimum_payment_amount = 300
+		pr.maximum_payment_amount = 600
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.get("invoices")), 2)
+		self.assertEqual(len(pr.get("payments")), 3)
+
+		pr.minimum_invoice_amount = 300
+		pr.maximum_invoice_amount = 600
+		pr.minimum_payment_amount = 400
+		pr.maximum_payment_amount = 500
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.get("invoices")), 3)
+		self.assertEqual(len(pr.get("payments")), 2)
+
+		pr.minimum_invoice_amount = (
+			pr.maximum_invoice_amount
+		) = pr.minimum_payment_amount = pr.maximum_payment_amount = 0
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.get("invoices")), 3)
+		self.assertEqual(len(pr.get("payments")), 3)
+
+	def test_filter_posting_date(self):
+		# check filter condition using transaction date
+		date1 = nowdate()
+		date2 = add_days(nowdate(), -1)
+		amount = 100
+		self.create_sales_invoice(qty=1, rate=amount, posting_date=date1)
+		si2 = self.create_sales_invoice(
+			qty=1, rate=amount, posting_date=date2, do_not_save=True, do_not_submit=True
+		)
+		si2.set_posting_time = 1
+		si2.posting_date = date2
+		si2.save().submit()
+		self.create_payment_entry(amount=amount, posting_date=date1).save().submit()
+		self.create_payment_entry(amount=amount, posting_date=date2).save().submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.from_invoice_date = pr.to_invoice_date = date1
+		pr.from_payment_date = pr.to_payment_date = date1
+
+		pr.get_unreconciled_entries()
+		# assert only si and pe are fetched
+		self.assertEqual(len(pr.get("invoices")), 1)
+		self.assertEqual(len(pr.get("payments")), 1)
+
+		pr.from_invoice_date = date2
+		pr.to_invoice_date = date1
+		pr.from_payment_date = date2
+		pr.to_payment_date = date1
+
+		pr.get_unreconciled_entries()
+		# assert only si and pe are fetched
+		self.assertEqual(len(pr.get("invoices")), 2)
+		self.assertEqual(len(pr.get("payments")), 2)
+
+	def test_filter_invoice_limit(self):
+		# check filter condition - invoice limit
+		transaction_date = nowdate()
+		rate = 100
+		invoices = []
+		payments = []
+		for i in range(5):
+			invoices.append(self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date))
+			pe = self.create_payment_entry(amount=rate, posting_date=transaction_date).save().submit()
+			payments.append(pe)
+
+		pr = self.create_payment_reconciliation()
+		pr.from_invoice_date = pr.to_invoice_date = transaction_date
+		pr.from_payment_date = pr.to_payment_date = transaction_date
+		pr.invoice_limit = 2
+		pr.payment_limit = 3
+		pr.get_unreconciled_entries()
+
+		self.assertEqual(len(pr.get("invoices")), 2)
+		self.assertEqual(len(pr.get("payments")), 3)
+
+	def test_payment_against_invoice(self):
+		si = self.create_sales_invoice(qty=1, rate=200)
+		pe = self.create_payment_entry(amount=55).save().submit()
+		# second payment entry
+		self.create_payment_entry(amount=35).save().submit()
+
+		pr = self.create_payment_reconciliation()
+
+		# reconcile multiple payments against invoice
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		si.reload()
+		self.assertEqual(si.status, "Partly Paid")
+		# check PR tool output post reconciliation
+		self.assertEqual(len(pr.get("invoices")), 1)
+		self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 110)
+		self.assertEqual(pr.get("payments"), [])
+
+		# cancel one PE
+		pe.reload()
+		pe.cancel()
+		pr.get_unreconciled_entries()
+		# check PR tool output
+		self.assertEqual(len(pr.get("invoices")), 1)
+		self.assertEqual(len(pr.get("payments")), 0)
+		self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 165)
+
+	def test_payment_against_journal(self):
+		transaction_date = nowdate()
+
+		sales = "Sales - _PR"
+		amount = 921
+		# debit debtors account to record an invoice
+		je = self.create_journal_entry(self.debit_to, sales, amount, transaction_date)
+		je.accounts[0].party_type = "Customer"
+		je.accounts[0].party = self.customer
+		je.save()
+		je.submit()
+
+		self.create_payment_entry(amount=amount, posting_date=transaction_date).save().submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.minimum_invoice_amount = pr.maximum_invoice_amount = amount
+		pr.from_invoice_date = pr.to_invoice_date = transaction_date
+		pr.from_payment_date = pr.to_payment_date = transaction_date
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		# check PR tool output
+		self.assertEqual(len(pr.get("invoices")), 0)
+		self.assertEqual(len(pr.get("payments")), 0)
+
+	def test_journal_against_invoice(self):
+		transaction_date = nowdate()
+		amount = 100
+		si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+
+		# credit debtors account to record a payment
+		je = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
+		je.accounts[1].party_type = "Customer"
+		je.accounts[1].party = self.customer
+		je.save()
+		je.submit()
+
+		pr = self.create_payment_reconciliation()
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		# assert outstanding
+		si.reload()
+		self.assertEqual(si.status, "Paid")
+		self.assertEqual(si.outstanding_amount, 0)
+
+		# check PR tool output
+		self.assertEqual(len(pr.get("invoices")), 0)
+		self.assertEqual(len(pr.get("payments")), 0)
+
+	def test_journal_against_journal(self):
+		transaction_date = nowdate()
+		sales = "Sales - _PR"
+		amount = 100
+
+		# debit debtors account to simulate a invoice
+		je1 = self.create_journal_entry(self.debit_to, sales, amount, transaction_date)
+		je1.accounts[0].party_type = "Customer"
+		je1.accounts[0].party = self.customer
+		je1.save()
+		je1.submit()
+
+		# credit debtors account to simulate a payment
+		je2 = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
+		je2.accounts[1].party_type = "Customer"
+		je2.accounts[1].party = self.customer
+		je2.save()
+		je2.submit()
+
+		pr = self.create_payment_reconciliation()
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		self.assertEqual(pr.get("invoices"), [])
+		self.assertEqual(pr.get("payments"), [])
+
+	def test_cr_note_against_invoice(self):
+		transaction_date = nowdate()
+		amount = 100
+
+		si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+
+		cr_note = self.create_sales_invoice(
+			qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+		)
+		cr_note.is_return = 1
+		cr_note = cr_note.save().submit()
+
+		pr = self.create_payment_reconciliation()
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+
+		pr.get_unreconciled_entries()
+		# check reconciliation tool output
+		# reconciled invoice and credit note shouldn't show up in selection
+		self.assertEqual(pr.get("invoices"), [])
+		self.assertEqual(pr.get("payments"), [])
+
+		# assert outstanding
+		si.reload()
+		self.assertEqual(si.status, "Paid")
+		self.assertEqual(si.outstanding_amount, 0)
+
+	def test_cr_note_partial_against_invoice(self):
+		transaction_date = nowdate()
+		amount = 100
+		allocated_amount = 80
+
+		si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+
+		cr_note = self.create_sales_invoice(
+			qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+		)
+		cr_note.is_return = 1
+		cr_note = cr_note.save().submit()
+
+		pr = self.create_payment_reconciliation()
+
+		pr.get_unreconciled_entries()
+		invoices = [x.as_dict() for x in pr.get("invoices")]
+		payments = [x.as_dict() for x in pr.get("payments")]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.allocation[0].allocated_amount = allocated_amount
+		pr.reconcile()
+
+		# assert outstanding
+		si.reload()
+		self.assertEqual(si.status, "Partly Paid")
+		self.assertEqual(si.outstanding_amount, 20)
+
+		pr.get_unreconciled_entries()
+		# check reconciliation tool output
+		self.assertEqual(len(pr.get("invoices")), 1)
+		self.assertEqual(len(pr.get("payments")), 1)
+		self.assertEqual(pr.get("invoices")[0].outstanding_amount, 20)
+		self.assertEqual(pr.get("payments")[0].amount, 20)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 23ad223..4e0d1c9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -165,17 +165,6 @@
 
 		super(PurchaseInvoice, self).set_missing_values(for_validate)
 
-	def check_conversion_rate(self):
-		default_currency = erpnext.get_company_currency(self.company)
-		if not default_currency:
-			throw(_("Please enter default currency in Company Master"))
-		if (
-			(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
-			or not self.conversion_rate
-			or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
-		):
-			throw(_("Conversion rate cannot be 0 or 1"))
-
 	def validate_credit_to_acc(self):
 		if not self.credit_to:
 			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index a580d45..1a3164b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -114,6 +114,7 @@
 		self.set_income_account_for_fixed_assets()
 		self.validate_item_cost_centers()
 		self.validate_income_account()
+		self.check_conversion_rate()
 
 		validate_inter_company_party(
 			self.doctype, self.customer, self.company, self.inter_company_invoice_reference
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index b8154dd..1b20c29 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1583,6 +1583,17 @@
 
 		self.assertTrue(gle)
 
+	def test_invoice_exchange_rate(self):
+		si = create_sales_invoice(
+			customer="_Test Customer USD",
+			debit_to="_Test Receivable USD - _TC",
+			currency="USD",
+			conversion_rate=1,
+			do_not_save=1,
+		)
+
+		self.assertRaises(frappe.ValidationError, si.save)
+
 	def test_invalid_currency(self):
 		# Customer currency = USD
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index b0513f1..8146804 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -35,7 +35,13 @@
 			validate_disabled_accounts(gl_map)
 			gl_map = process_gl_map(gl_map, merge_entries)
 			if gl_map and len(gl_map) > 1:
-				create_payment_ledger_entry(gl_map)
+				create_payment_ledger_entry(
+					gl_map,
+					cancel=0,
+					adv_adj=adv_adj,
+					update_outstanding=update_outstanding,
+					from_repost=from_repost,
+				)
 				save_entries(gl_map, adv_adj, update_outstanding, from_repost)
 			# Post GL Map proccess there may no be any GL Entries
 			elif gl_map:
@@ -482,6 +488,9 @@
 
 	if gl_entries:
 		create_payment_ledger_entry(gl_entries, cancel=1)
+		create_payment_ledger_entry(
+			gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
+		)
 		validate_accounting_period(gl_entries)
 		check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
 		set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 41f3223..8daff9d 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -9,6 +9,8 @@
 import frappe.defaults
 from frappe import _, qb, throw
 from frappe.model.meta import get_field_precision
+from frappe.query_builder import AliasedQuery, Criterion, Table
+from frappe.query_builder.functions import Sum
 from frappe.query_builder.utils import DocType
 from frappe.utils import (
 	cint,
@@ -437,7 +439,8 @@
 		# cancel advance entry
 		doc = frappe.get_doc(voucher_type, voucher_no)
 		frappe.flags.ignore_party_validation = True
-		doc.make_gl_entries(cancel=1, adv_adj=1)
+		gl_map = doc.build_gl_map()
+		create_payment_ledger_entry(gl_map, cancel=1, adv_adj=1)
 
 		for entry in entries:
 			check_if_advance_entry_modified(entry)
@@ -452,7 +455,9 @@
 		doc.save(ignore_permissions=True)
 		# re-submit advance entry
 		doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
-		doc.make_gl_entries(cancel=0, adv_adj=1)
+		gl_map = doc.build_gl_map()
+		create_payment_ledger_entry(gl_map, cancel=0, adv_adj=1)
+
 		frappe.flags.ignore_party_validation = False
 
 		if entry.voucher_type in ("Payment Entry", "Journal Entry"):
@@ -816,7 +821,11 @@
 	return held_invoices
 
 
-def get_outstanding_invoices(party_type, party, account, condition=None, filters=None):
+def get_outstanding_invoices(
+	party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None
+):
+
+	ple = qb.DocType("Payment Ledger Entry")
 	outstanding_invoices = []
 	precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
 
@@ -829,76 +838,30 @@
 	else:
 		party_account_type = erpnext.get_party_account_type(party_type)
 
-	if party_account_type == "Receivable":
-		dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
-		payment_dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
-	else:
-		dr_or_cr = "credit_in_account_currency - debit_in_account_currency"
-		payment_dr_or_cr = "debit_in_account_currency - credit_in_account_currency"
-
 	held_invoices = get_held_invoices(party_type, party)
 
-	invoice_list = frappe.db.sql(
-		"""
-		select
-			voucher_no, voucher_type, posting_date, due_date,
-			ifnull(sum({dr_or_cr}), 0) as invoice_amount,
-			account_currency as currency
-		from
-			`tabGL Entry`
-		where
-			party_type = %(party_type)s and party = %(party)s
-			and account = %(account)s and {dr_or_cr} > 0
-			and is_cancelled=0
-			{condition}
-			and ((voucher_type = 'Journal Entry'
-					and (against_voucher = '' or against_voucher is null))
-				or (voucher_type not in ('Journal Entry', 'Payment Entry')))
-		group by voucher_type, voucher_no
-		order by posting_date, name""".format(
-			dr_or_cr=dr_or_cr, condition=condition or ""
-		),
-		{
-			"party_type": party_type,
-			"party": party,
-			"account": account,
-		},
-		as_dict=True,
-	)
+	common_filter = common_filter or []
+	common_filter.append(ple.account_type == party_account_type)
+	common_filter.append(ple.account == account)
+	common_filter.append(ple.party_type == party_type)
+	common_filter.append(ple.party == party)
 
-	payment_entries = frappe.db.sql(
-		"""
-		select against_voucher_type, against_voucher,
-			ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
-		from `tabGL Entry`
-		where party_type = %(party_type)s and party = %(party)s
-			and account = %(account)s
-			and {payment_dr_or_cr} > 0
-			and against_voucher is not null and against_voucher != ''
-			and is_cancelled=0
-		group by against_voucher_type, against_voucher
-	""".format(
-			payment_dr_or_cr=payment_dr_or_cr
-		),
-		{"party_type": party_type, "party": party, "account": account},
-		as_dict=True,
+	ple_query = QueryPaymentLedger()
+	invoice_list = ple_query.get_voucher_outstandings(
+		common_filter=common_filter,
+		min_outstanding=min_outstanding,
+		max_outstanding=max_outstanding,
+		get_invoices=True,
 	)
 
-	pe_map = frappe._dict()
-	for d in payment_entries:
-		pe_map.setdefault((d.against_voucher_type, d.against_voucher), d.payment_amount)
-
 	for d in invoice_list:
-		payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
-		outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
+		payment_amount = d.invoice_amount - d.outstanding
+		outstanding_amount = d.outstanding
 		if outstanding_amount > 0.5 / (10**precision):
 			if (
-				filters
-				and filters.get("outstanding_amt_greater_than")
-				and not (
-					outstanding_amount >= filters.get("outstanding_amt_greater_than")
-					and outstanding_amount <= filters.get("outstanding_amt_less_than")
-				)
+				min_outstanding
+				and max_outstanding
+				and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding)
 			):
 				continue
 
@@ -1389,7 +1352,9 @@
 			frappe.delete_doc("Desktop Icon", icon)
 
 
-def create_payment_ledger_entry(gl_entries, cancel=0):
+def create_payment_ledger_entry(
+	gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
+):
 	if gl_entries:
 		ple = None
 
@@ -1462,9 +1427,42 @@
 				if cancel:
 					delink_original_entry(ple)
 				ple.flags.ignore_permissions = 1
+				ple.flags.adv_adj = adv_adj
+				ple.flags.from_repost = from_repost
+				ple.flags.update_outstanding = update_outstanding
 				ple.submit()
 
 
+def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
+	ple = frappe.qb.DocType("Payment Ledger Entry")
+	vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
+	common_filter = []
+	if account:
+		common_filter.append(ple.account == account)
+
+	if party_type:
+		common_filter.append(ple.party_type == party_type)
+
+	if party:
+		common_filter.append(ple.party == party)
+
+	ple_query = QueryPaymentLedger()
+
+	# on cancellation outstanding can be an empty list
+	voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter)
+	if voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"] and voucher_outstanding:
+		outstanding = voucher_outstanding[0]
+		ref_doc = frappe.get_doc(voucher_type, voucher_no)
+
+		# Didn't use db_set for optimisation purpose
+		ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
+		frappe.db.set_value(
+			voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
+		)
+
+		ref_doc.set_status(update=True)
+
+
 def delink_original_entry(pl_entry):
 	if pl_entry:
 		ple = qb.DocType("Payment Ledger Entry")
@@ -1486,3 +1484,196 @@
 			)
 		)
 		query.run()
+
+
+class QueryPaymentLedger(object):
+	"""
+	Helper Class for Querying Payment Ledger Entry
+	"""
+
+	def __init__(self):
+		self.ple = qb.DocType("Payment Ledger Entry")
+
+		# query result
+		self.voucher_outstandings = []
+
+		# query filters
+		self.vouchers = []
+		self.common_filter = []
+		self.min_outstanding = None
+		self.max_outstanding = None
+
+	def reset(self):
+		# clear filters
+		self.vouchers.clear()
+		self.common_filter.clear()
+		self.min_outstanding = self.max_outstanding = None
+
+		# clear result
+		self.voucher_outstandings.clear()
+
+	def query_for_outstanding(self):
+		"""
+		Database query to fetch voucher amount and voucher outstanding using Common Table Expression
+		"""
+
+		ple = self.ple
+
+		filter_on_voucher_no = []
+		filter_on_against_voucher_no = []
+		if self.vouchers:
+			voucher_types = set([x.voucher_type for x in self.vouchers])
+			voucher_nos = set([x.voucher_no for x in self.vouchers])
+
+			filter_on_voucher_no.append(ple.voucher_type.isin(voucher_types))
+			filter_on_voucher_no.append(ple.voucher_no.isin(voucher_nos))
+
+			filter_on_against_voucher_no.append(ple.against_voucher_type.isin(voucher_types))
+			filter_on_against_voucher_no.append(ple.against_voucher_no.isin(voucher_nos))
+
+		# build outstanding amount filter
+		filter_on_outstanding_amount = []
+		if self.min_outstanding:
+			if self.min_outstanding > 0:
+				filter_on_outstanding_amount.append(
+					Table("outstanding").amount_in_account_currency >= self.min_outstanding
+				)
+			else:
+				filter_on_outstanding_amount.append(
+					Table("outstanding").amount_in_account_currency <= self.min_outstanding
+				)
+		if self.max_outstanding:
+			if self.max_outstanding > 0:
+				filter_on_outstanding_amount.append(
+					Table("outstanding").amount_in_account_currency <= self.max_outstanding
+				)
+			else:
+				filter_on_outstanding_amount.append(
+					Table("outstanding").amount_in_account_currency >= self.max_outstanding
+				)
+
+		# build query for voucher amount
+		query_voucher_amount = (
+			qb.from_(ple)
+			.select(
+				ple.account,
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.party_type,
+				ple.party,
+				ple.posting_date,
+				ple.due_date,
+				ple.account_currency.as_("currency"),
+				Sum(ple.amount).as_("amount"),
+				Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
+			)
+			.where(ple.delinked == 0)
+			.where(Criterion.all(filter_on_voucher_no))
+			.where(Criterion.all(self.common_filter))
+			.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
+		)
+
+		# build query for voucher outstanding
+		query_voucher_outstanding = (
+			qb.from_(ple)
+			.select(
+				ple.account,
+				ple.against_voucher_type.as_("voucher_type"),
+				ple.against_voucher_no.as_("voucher_no"),
+				ple.party_type,
+				ple.party,
+				ple.posting_date,
+				ple.due_date,
+				ple.account_currency.as_("currency"),
+				Sum(ple.amount).as_("amount"),
+				Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
+			)
+			.where(ple.delinked == 0)
+			.where(Criterion.all(filter_on_against_voucher_no))
+			.where(Criterion.all(self.common_filter))
+			.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
+		)
+
+		# build CTE for combining voucher amount and outstanding
+		self.cte_query_voucher_amount_and_outstanding = (
+			qb.with_(query_voucher_amount, "vouchers")
+			.with_(query_voucher_outstanding, "outstanding")
+			.from_(AliasedQuery("vouchers"))
+			.left_join(AliasedQuery("outstanding"))
+			.on(
+				(AliasedQuery("vouchers").account == AliasedQuery("outstanding").account)
+				& (AliasedQuery("vouchers").voucher_type == AliasedQuery("outstanding").voucher_type)
+				& (AliasedQuery("vouchers").voucher_no == AliasedQuery("outstanding").voucher_no)
+				& (AliasedQuery("vouchers").party_type == AliasedQuery("outstanding").party_type)
+				& (AliasedQuery("vouchers").party == AliasedQuery("outstanding").party)
+			)
+			.select(
+				Table("vouchers").account,
+				Table("vouchers").voucher_type,
+				Table("vouchers").voucher_no,
+				Table("vouchers").party_type,
+				Table("vouchers").party,
+				Table("vouchers").posting_date,
+				Table("vouchers").amount.as_("invoice_amount"),
+				Table("vouchers").amount_in_account_currency.as_("invoice_amount_in_account_currency"),
+				Table("outstanding").amount.as_("outstanding"),
+				Table("outstanding").amount_in_account_currency.as_("outstanding_in_account_currency"),
+				(Table("vouchers").amount - Table("outstanding").amount).as_("paid_amount"),
+				(
+					Table("vouchers").amount_in_account_currency - Table("outstanding").amount_in_account_currency
+				).as_("paid_amount_in_account_currency"),
+				Table("vouchers").due_date,
+				Table("vouchers").currency,
+			)
+			.where(Criterion.all(filter_on_outstanding_amount))
+		)
+
+		# build CTE filter
+		# only fetch invoices
+		if self.get_invoices:
+			self.cte_query_voucher_amount_and_outstanding = (
+				self.cte_query_voucher_amount_and_outstanding.having(
+					qb.Field("outstanding_in_account_currency") > 0
+				)
+			)
+		# only fetch payments
+		elif self.get_payments:
+			self.cte_query_voucher_amount_and_outstanding = (
+				self.cte_query_voucher_amount_and_outstanding.having(
+					qb.Field("outstanding_in_account_currency") < 0
+				)
+			)
+
+		# execute SQL
+		self.voucher_outstandings = self.cte_query_voucher_amount_and_outstanding.run(as_dict=True)
+
+	def get_voucher_outstandings(
+		self,
+		vouchers=None,
+		common_filter=None,
+		min_outstanding=None,
+		max_outstanding=None,
+		get_payments=False,
+		get_invoices=False,
+	):
+		"""
+		Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE
+
+		vouchers - dict of vouchers to get
+		common_filter - array of criterions
+		min_outstanding - filter on minimum total outstanding amount
+		max_outstanding - filter on maximum total  outstanding amount
+		get_invoices - only fetch vouchers(ledger entries with +ve outstanding)
+		get_payments - only fetch payments(ledger entries with -ve outstanding)
+		"""
+
+		self.reset()
+		self.vouchers = vouchers
+		self.common_filter = common_filter or []
+		self.min_outstanding = min_outstanding
+		self.max_outstanding = max_outstanding
+		self.get_payments = get_payments
+		self.get_invoices = get_invoices
+		self.query_for_outstanding()
+
+		return self.voucher_outstandings
diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
index ca3be03..721e54e 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -59,6 +59,7 @@
 				for (let option of status){
 					options.push({
 						"value": option,
+						"label": __(option),
 						"description": ""
 					})
 				}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index ded9a30..fc6fdcd 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1848,6 +1848,17 @@
 		jv.save()
 		jv.submit()
 
+	def check_conversion_rate(self):
+		default_currency = erpnext.get_company_currency(self.company)
+		if not default_currency:
+			throw(_("Please enter default currency in Company Master"))
+		if (
+			(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
+			or not self.conversion_rate
+			or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
+		):
+			throw(_("Conversion rate cannot be 0 or 1"))
+
 
 @frappe.whitelist()
 def get_tax_rate(account_head):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 5ba314e..243ebb6 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -29,8 +29,8 @@
 				or employee_name like %(txt)s)
 			{fcond} {mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, employee_name) > 0 then locate(%(_txt)s, employee_name) else 99999 end),
 			idx desc,
 			name, employee_name
 		limit %(page_len)s offset %(start)s""".format(
@@ -60,9 +60,9 @@
 				or company_name like %(txt)s)
 			{mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, lead_name), locate(%(_txt)s, lead_name), 99999),
-			if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, lead_name) > 0 then locate(%(_txt)s, lead_name) else 99999 end),
+			(case when locate(%(_txt)s, company_name) > 0 then locate(%(_txt)s, company_name) else 99999 end),
 			idx desc,
 			name, lead_name
 		limit %(page_len)s offset %(start)s""".format(
@@ -96,8 +96,8 @@
 			and ({scond}) and disabled=0
 			{fcond} {mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, customer_name), locate(%(_txt)s, customer_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, customer_name) > 0 then locate(%(_txt)s, customer_name) else 99999 end),
 			idx desc,
 			name, customer_name
 		limit %(page_len)s offset %(start)s""".format(
@@ -130,11 +130,11 @@
 		where docstatus < 2
 			and ({key} like %(txt)s
 			or supplier_name like %(txt)s) and disabled=0
-			and (on_hold = 0 or (on_hold = 1 and CURDATE() > release_date))
+			and (on_hold = 0 or (on_hold = 1 and CURRENT_DATE > release_date))
 			{mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, supplier_name) > 0 then locate(%(_txt)s, supplier_name) else 99999 end),
 			idx desc,
 			name, supplier_name
 		limit %(page_len)s offset %(start)s""".format(
@@ -305,15 +305,15 @@
 
 	return frappe.db.sql(
 		"""select {fields}
-		from tabBOM
-		where tabBOM.docstatus=1
-			and tabBOM.is_active=1
-			and tabBOM.`{key}` like %(txt)s
+		from `tabBOM`
+		where `tabBOM`.docstatus=1
+			and `tabBOM`.is_active=1
+			and `tabBOM`.`{key}` like %(txt)s
 			{fcond} {mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
 			idx desc, name
-		limit %(start)s, %(page_len)s """.format(
+		limit %(page_len)s offset %(start)s""".format(
 			fields=", ".join(fields),
 			fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
 			mcond=get_match_cond(doctype).replace("%", "%%"),
@@ -348,8 +348,8 @@
 			`tabProject`.status not in ('Completed', 'Cancelled')
 			and {cond} {scond} {match_cond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			idx desc,
+			(case when locate(%(_txt)s, `tabProject`.name) > 0 then locate(%(_txt)s, `tabProject`.name) else 99999 end),
+			`tabProject`.idx desc,
 			`tabProject`.name asc
 		limit {page_len} offset {start}""".format(
 			fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 76a25a0..197d2ba 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -443,7 +443,7 @@
 				"""update `tab%(target_parent_dt)s`
 				set %(target_parent_field)s = round(
 					ifnull((select
-						ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0)
+						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)
 					%(update_modified)s
@@ -455,9 +455,9 @@
 			if args.get("status_field"):
 				frappe.db.sql(
 					"""update `tab%(target_parent_dt)s`
-					set %(status_field)s = if(%(target_parent_field)s<0.001,
-						'Not %(keyword)s', if(%(target_parent_field)s>=99.999999,
-						'Fully %(keyword)s', 'Partly %(keyword)s'))
+					set %(status_field)s = (case when %(target_parent_field)s<0.001 then 'Not %(keyword)s'
+					else case when %(target_parent_field)s>=99.999999 then 'Fully %(keyword)s'
+					else 'Partly %(keyword)s' end end)
 					where name='%(name)s'"""
 					% args
 				)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index feec42f..e90a4f6 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -166,7 +166,7 @@
 									"against": warehouse_account[sle.warehouse]["account"],
 									"cost_center": item_row.cost_center,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-									"credit": flt(sle.stock_value_difference, precision),
+									"debit": -1 * flt(sle.stock_value_difference, precision),
 									"project": item_row.get("project") or self.get("project"),
 									"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
 								},
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 4c88eca..b29f671 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -445,6 +445,7 @@
 			and self.is_active
 		):
 			frappe.db.set(self, "is_default", 1)
+			frappe.db.set_value("Item", self.item, "default_bom", self.name)
 		else:
 			frappe.db.set(self, "is_default", 0)
 			item = frappe.get_doc("Item", self.item)
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 182a20c..860512c 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -559,6 +559,42 @@
 		bom.submit()
 		self.assertEqual(bom.items[0].rate, 42)
 
+	def test_set_default_bom_for_item_having_single_bom(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		fg_item = make_item(properties={"is_stock_item": 1})
+		bom_item = make_item(properties={"is_stock_item": 1})
+
+		# Step 1: Create BOM
+		bom = frappe.new_doc("BOM")
+		bom.item = fg_item.item_code
+		bom.quantity = 1
+		bom.append(
+			"items",
+			{
+				"item_code": bom_item.item_code,
+				"qty": 1,
+				"uom": bom_item.stock_uom,
+				"stock_uom": bom_item.stock_uom,
+				"rate": 100.0,
+			},
+		)
+		bom.save()
+		bom.submit()
+		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+
+		# Step 2: Uncheck is_active field
+		bom.is_active = 0
+		bom.save()
+		bom.reload()
+		self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom"))
+
+		# Step 3: Check is_active field
+		bom.is_active = 1
+		bom.save()
+		bom.reload()
+		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+
 
 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/bom_update_log/bom_update_log.json b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
index c32e383..a926e69 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
@@ -7,11 +7,11 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "current_bom",
-  "new_bom",
-  "column_break_3",
   "update_type",
   "status",
+  "column_break_3",
+  "current_bom",
+  "new_bom",
   "error_log",
   "progress_section",
   "current_level",
@@ -37,6 +37,7 @@
    "options": "BOM"
   },
   {
+   "depends_on": "eval:doc.update_type === \"Replace BOM\"",
    "fieldname": "column_break_3",
    "fieldtype": "Column Break"
   },
@@ -87,6 +88,7 @@
    "options": "BOM Update Batch"
   },
   {
+   "depends_on": "eval:doc.status !== \"Completed\"",
    "fieldname": "current_level",
    "fieldtype": "Int",
    "label": "Current Level"
@@ -96,7 +98,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-06-06 15:15:23.883251",
+ "modified": "2022-06-20 15:43:55.696388",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Update Log",
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 9c9c240..c3f52d4 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -6,6 +6,8 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Now
 from frappe.utils import cint, cstr
 
 from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
@@ -22,6 +24,17 @@
 
 
 class BOMUpdateLog(Document):
+	@staticmethod
+	def clear_old_logs(days=None):
+		days = days or 90
+		table = DocType("BOM Update Log")
+		frappe.db.delete(
+			table,
+			filters=(
+				(table.modified < (Now() - Interval(days=days))) & (table.update_type == "Update Cost")
+			),
+		)
+
 	def validate(self):
 		if self.update_type == "Replace BOM":
 			self.validate_boms_are_specified()
@@ -77,7 +90,11 @@
 				now=frappe.flags.in_test,
 			)
 		else:
-			process_boms_cost_level_wise(self)
+			frappe.enqueue(
+				method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
+				update_doc=self,
+				now=frappe.flags.in_test,
+			)
 
 
 def run_replace_bom_job(
@@ -112,28 +129,31 @@
 	current_boms = {}
 	values = {}
 
-	if update_doc.status == "Queued":
-		# First level yet to process. On Submit.
-		current_level = 0
-		current_boms = get_leaf_boms()
-		values = {
-			"processed_boms": json.dumps({}),
-			"status": "In Progress",
-			"current_level": current_level,
-		}
-	else:
-		# Resume next level. via Cron Job.
-		if not parent_boms:
-			return
+	try:
+		if update_doc.status == "Queued":
+			# First level yet to process. On Submit.
+			current_level = 0
+			current_boms = get_leaf_boms()
+			values = {
+				"processed_boms": json.dumps({}),
+				"status": "In Progress",
+				"current_level": current_level,
+			}
+		else:
+			# Resume next level. via Cron Job.
+			if not parent_boms:
+				return
 
-		current_level = cint(update_doc.current_level) + 1
+			current_level = cint(update_doc.current_level) + 1
 
-		# Process the next level BOMs. Stage parents as current BOMs.
-		current_boms = parent_boms.copy()
-		values = {"current_level": current_level}
+			# Process the next level BOMs. Stage parents as current BOMs.
+			current_boms = parent_boms.copy()
+			values = {"current_level": current_level}
 
-	set_values_in_log(update_doc.name, values, commit=True)
-	queue_bom_cost_jobs(current_boms, update_doc, current_level)
+		set_values_in_log(update_doc.name, values, commit=True)
+		queue_bom_cost_jobs(current_boms, update_doc, current_level)
+	except Exception:
+		handle_exception(update_doc)
 
 
 def queue_bom_cost_jobs(
@@ -199,16 +219,22 @@
 		current_boms, processed_boms = get_processed_current_boms(log, bom_batches)
 		parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms)
 
-		# Unset processed BOMs if log is complete, it is used for next level BOMs
+		# Unset processed BOMs (it is used for next level BOMs) & change status if log is complete
+		status = "Completed" if not parent_boms else "In Progress"
+		processed_boms = json.dumps([] if not parent_boms else processed_boms)
 		set_values_in_log(
 			log.name,
 			values={
-				"processed_boms": json.dumps([] if not parent_boms else processed_boms),
-				"status": "Completed" if not parent_boms else "In Progress",
+				"processed_boms": processed_boms,
+				"status": status,
 			},
 			commit=True,
 		)
 
+		# clear progress section
+		if status == "Completed":
+			frappe.db.delete("BOM Update Batch", {"parent": log.name})
+
 		if parent_boms:  # there is a next level to process
 			process_boms_cost_level_wise(
 				update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
index e39b563..bc709d8 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
@@ -1,6 +1,6 @@
 frappe.listview_settings['BOM Update Log'] = {
 	add_fields: ["status"],
-	get_indicator: function(doc) {
+	get_indicator: (doc) => {
 		let status_map = {
 			"Queued": "orange",
 			"In Progress": "blue",
@@ -9,5 +9,22 @@
 		};
 
 		return [__(doc.status), status_map[doc.status], "status,=," + doc.status];
-	}
+	},
+	onload: () => {
+		if (!frappe.model.can_write("Log Settings")) {
+			return;
+		}
+
+		let sidebar_entry = $(
+			'<ul class="list-unstyled sidebar-menu log-retention-note"></ul>'
+		).appendTo(cur_list.page.sidebar);
+		let message = __("Note: Automatic log deletion only applies to logs of type <i>Update Cost</i>");
+		$(`<hr><div class='text-muted'>${message}</div>`).appendTo(sidebar_entry);
+
+		frappe.require("logtypes.bundle.js", () => {
+			frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+		});
+
+
+	},
 };
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 27e7e24..6bb4cfc 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1,6 +1,8 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
+import copy
+
 import frappe
 from frappe.tests.utils import FrappeTestCase, change_settings, timeout
 from frappe.utils import add_days, add_months, cint, flt, now, today
@@ -19,6 +21,7 @@
 )
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.stock.doctype.item.test_item import create_item, make_item
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.stock_entry import test_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.utils import get_bin
@@ -28,6 +31,7 @@
 	def setUp(self):
 		self.warehouse = "_Test Warehouse 2 - _TC"
 		self.item = "_Test Item"
+		prepare_data_for_backflush_based_on_materials_transferred()
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -527,6 +531,8 @@
 			work_order.cancel()
 
 	def test_work_order_with_non_transfer_item(self):
+		frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+
 		items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0}
 		for item, allow_transfer in items.items():
 			make_item(item, {"include_item_in_manufacturing": allow_transfer})
@@ -1071,7 +1077,7 @@
 		sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
 		for row in sm.get("items"):
 			if row.get("item_code") == "_Test Item":
-				row.qty = 110
+				row.qty = 120
 
 		sm.submit()
 		cancel_stock_entry.append(sm.name)
@@ -1079,21 +1085,21 @@
 		s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
 		for row in s.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 100)
+				self.assertEqual(row.get("qty"), 108)
 		s.submit()
 		cancel_stock_entry.append(s.name)
 
 		s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
 		for row in s1.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 5)
+				self.assertEqual(row.get("qty"), 6)
 		s1.submit()
 		cancel_stock_entry.append(s1.name)
 
 		s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
 		for row in s2.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 5)
+				self.assertEqual(row.get("qty"), 6)
 
 		cancel_stock_entry.reverse()
 		for ste in cancel_stock_entry:
@@ -1203,6 +1209,269 @@
 		self.assertEqual(work_order.required_items[0].transferred_qty, 1)
 		self.assertEqual(work_order.required_items[1].transferred_qty, 2)
 
+	def test_backflushed_batch_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		batch_item = "Test Batch MCC Keyboard"
+		fg_item = "Test FG Item with Batch Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
+		)
+
+		ste_doc.append(
+			"items",
+			{
+				"item_code": batch_item,
+				"item_name": batch_item,
+				"description": batch_item,
+				"basic_rate": 100,
+				"t_warehouse": "Stores - _TC",
+				"qty": 2,
+				"uom": "Nos",
+				"stock_uom": "Nos",
+				"conversion_factor": 1,
+			},
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.insert()
+		ste_doc.submit()
+
+		batch_list = [row.batch_no for row in ste_doc.items]
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].qty = 2
+		transferred_ste_doc.items[0].batch_no = batch_list[0]
+
+		new_row = copy.deepcopy(transferred_ste_doc.items[0])
+		new_row.name = ""
+		new_row.batch_no = batch_list[1]
+
+		# Transferred two batches from Stores to WIP Warehouse
+		transferred_ste_doc.append("items", new_row)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Batch no should be same as transferred Batch no
+		self.assertEqual(manufacture_ste_doc1.items[0].batch_no, batch_list[0])
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Batch no should be same as transferred Batch no
+		self.assertEqual(manufacture_ste_doc2.items[0].batch_no, batch_list[0])
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
+		self.assertEqual(manufacture_ste_doc2.items[1].batch_no, batch_list[1])
+		self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
+
+	def test_backflushed_serial_no_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		sn_item = "Test Serial No BTT Headphone"
+		fg_item = "Test FG Item with Serial No Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=sn_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.submit()
+
+		serial_nos_list = sorted(get_serial_nos(ste_doc.items[0].serial_no))
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].serial_no = "\n".join(serial_nos_list)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Serial nos should be same as transferred Serial nos
+		self.assertEqual(get_serial_nos(manufacture_ste_doc1.items[0].serial_no), serial_nos_list[0:1])
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Serial nos should be same as transferred Serial nos
+		self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:3])
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 2)
+
+	def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		sn_batch_item = "Test Batch Serial No WebCam"
+		fg_item = "Test FG Item with Serial & Batch No Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=sn_batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
+		)
+
+		ste_doc.append(
+			"items",
+			{
+				"item_code": sn_batch_item,
+				"item_name": sn_batch_item,
+				"description": sn_batch_item,
+				"basic_rate": 100,
+				"t_warehouse": "Stores - _TC",
+				"qty": 2,
+				"uom": "Nos",
+				"stock_uom": "Nos",
+				"conversion_factor": 1,
+			},
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.insert()
+		ste_doc.submit()
+
+		batch_dict = {row.batch_no: get_serial_nos(row.serial_no) for row in ste_doc.items}
+		batches = list(batch_dict.keys())
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].qty = 2
+		transferred_ste_doc.items[0].batch_no = batches[0]
+		transferred_ste_doc.items[0].serial_no = "\n".join(batch_dict.get(batches[0]))
+
+		new_row = copy.deepcopy(transferred_ste_doc.items[0])
+		new_row.name = ""
+		new_row.batch_no = batches[1]
+		new_row.serial_no = "\n".join(batch_dict.get(batches[1]))
+
+		# Transferred two batches from Stores to WIP Warehouse
+		transferred_ste_doc.append("items", new_row)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
+		batch_no = manufacture_ste_doc1.items[0].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc1.items[0].serial_no)[0], batch_dict.get(batch_no)[0]
+		)
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
+		batch_no = manufacture_ste_doc2.items[0].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc2.items[0].serial_no)[0], batch_dict.get(batch_no)[1]
+		)
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
+
+		batch_no = manufacture_ste_doc2.items[1].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc2.items[1].serial_no)[0], batch_dict.get(batch_no)[0]
+		)
+		self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
+
+
+def prepare_data_for_backflush_based_on_materials_transferred():
+	batch_item_doc = make_item(
+		"Test Batch MCC Keyboard",
+		{
+			"is_stock_item": 1,
+			"has_batch_no": 1,
+			"create_new_batch": 1,
+			"batch_number_series": "TBMK.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Batch Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[batch_item_doc.name])
+
+	sn_item_doc = make_item(
+		"Test Serial No BTT Headphone",
+		{
+			"is_stock_item": 1,
+			"has_serial_no": 1,
+			"serial_no_series": "TSBH.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Serial No Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_item_doc.name])
+
+	sn_batch_item_doc = make_item(
+		"Test Batch Serial No WebCam",
+		{
+			"is_stock_item": 1,
+			"has_batch_no": 1,
+			"create_new_batch": 1,
+			"batch_number_series": "TBSW.#####",
+			"has_serial_no": 1,
+			"serial_no_series": "TBSWC.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Serial & Batch No Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
+
 
 def update_job_card(job_card, jc_qty=None):
 	employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
index 1e0d20d..e15aa4a 100644
--- a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -1,6 +1,6 @@
 import frappe
 from frappe import qb
-from frappe.query_builder import Case
+from frappe.query_builder import Case, CustomFunction
 from frappe.query_builder.custom import ConstantColumn
 from frappe.query_builder.functions import IfNull
 
@@ -87,6 +87,7 @@
 
 		gl = qb.DocType("GL Entry")
 		account = qb.DocType("Account")
+		ifelse = CustomFunction("IF", ["condition", "then", "else"])
 
 		gl_entries = (
 			qb.from_(gl)
@@ -96,8 +97,12 @@
 				gl.star,
 				ConstantColumn(1).as_("docstatus"),
 				account.account_type.as_("account_type"),
-				IfNull(gl.against_voucher_type, gl.voucher_type).as_("against_voucher_type"),
-				IfNull(gl.against_voucher, gl.voucher_no).as_("against_voucher_no"),
+				IfNull(
+					ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
+				).as_("against_voucher_type"),
+				IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
+					"against_voucher_no"
+				),
 				# convert debit/credit to amount
 				Case()
 				.when(account.account_type == "Receivable", gl.debit - gl.credit)
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index a0cef70..86a8c12 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -1035,8 +1035,8 @@
 			{emp_cond}
 			{fcond} {mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, employee_name) > 0 then locate(%(_txt)s, employee_name) else 99999 end),
 			idx desc,
 			name, employee_name
 		limit %(page_len)s offset %(start)s""".format(
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index c613fe6..7aa56de 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -387,8 +387,8 @@
 				or full_name like %(txt)s)
 			{fcond} {mcond}
 		order by
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
-			if(locate(%(_txt)s, full_name), locate(%(_txt)s, full_name), 99999),
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
+			(case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end)
 			idx desc,
 			name, full_name
 		limit %(page_len)s offset %(start)s""".format(
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 2458756..7dc3fab 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -375,6 +375,12 @@
 	if not allowed_to_interact_with:
 		allowed_to_interact_with = represents_company
 
+	exisiting_representative = frappe.db.get_value(
+		"Customer", {"represents_company": represents_company}
+	)
+	if exisiting_representative:
+		return exisiting_representative
+
 	if not frappe.db.exists("Customer", customer_name):
 		customer = frappe.get_doc(
 			{
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index f6877e9..4fa4515 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -127,7 +127,7 @@
 
 	@frappe.whitelist()
 	def declare_enquiry_lost(self, lost_reasons_list, competitors, detailed_reason=None):
-		if not self.has_sales_order():
+		if not (self.is_fully_ordered() or self.is_partially_ordered()):
 			get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
 			lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
 			frappe.db.set(self, "status", "Lost")
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
index 76a5bb5..91748bc 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
@@ -55,6 +55,7 @@
 				for (let option of status){
 					options.push({
 						"value": option,
+						"label": __(option),
 						"description": ""
 					})
 				}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index fffcdca..6bcab73 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1064,6 +1064,33 @@
 
 		self.assertEqual(dn.items[0].rate, rate)
 
+	def test_internal_transfer_precision_gle(self):
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
+		item = make_item(properties={"valuation_method": "Moving Average"}).name
+		company = "_Test Company with perpetual inventory"
+		warehouse = "Stores - TCP1"
+		target = "Finished Goods - TCP1"
+		customer = create_internal_customer(represents_company=company)
+
+		# average rate = 128.015
+		rates = [101.45, 150.46, 138.25, 121.9]
+
+		for rate in rates:
+			make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate)
+
+		dn = create_delivery_note(
+			item_code=item,
+			company=company,
+			customer=customer,
+			qty=4,
+			warehouse=warehouse,
+			target_warehouse=target,
+		)
+		self.assertFalse(
+			frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
+		)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 7dc3ba0..d31d695 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -699,7 +699,7 @@
 			AND `company` = %(company)s
 			AND `name` like %(txt)s
 		ORDER BY
-			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
+			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end) name
 		LIMIT
 			%(start)s, %(page_length)s""",
 		{
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index e902d1e..4b2850e 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -596,21 +596,6 @@
 					title=_("Insufficient Stock"),
 				)
 
-	def set_serial_nos(self, work_order):
-		previous_se = frappe.db.get_value(
-			"Stock Entry",
-			{"work_order": work_order, "purpose": "Material Transfer for Manufacture"},
-			"name",
-		)
-
-		for d in self.get("items"):
-			transferred_serial_no = frappe.db.get_value(
-				"Stock Entry Detail", {"parent": previous_se, "item_code": d.item_code}, "serial_no"
-			)
-
-			if transferred_serial_no:
-				d.serial_no = transferred_serial_no
-
 	@frappe.whitelist()
 	def get_stock_and_rate(self):
 		"""
@@ -1321,7 +1306,7 @@
 					and not self.pro_doc.skip_transfer
 					and self.flags.backflush_based_on == "Material Transferred for Manufacture"
 				):
-					self.get_transfered_raw_materials()
+					self.add_transfered_raw_materials_in_items()
 
 				elif (
 					self.work_order
@@ -1365,7 +1350,6 @@
 
 			# fetch the serial_no of the first stock entry for the second stock entry
 			if self.work_order and self.purpose == "Manufacture":
-				self.set_serial_nos(self.work_order)
 				work_order = frappe.get_doc("Work Order", self.work_order)
 				add_additional_cost(self, work_order)
 
@@ -1655,119 +1639,78 @@
 					}
 				)
 
-	def get_transfered_raw_materials(self):
-		transferred_materials = frappe.db.sql(
-			"""
-			select
-				item_name, original_item, item_code, sum(qty) as qty, sed.t_warehouse as warehouse,
-				description, stock_uom, expense_account, cost_center
-			from `tabStock Entry` se,`tabStock Entry Detail` sed
-			where
-				se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
-				and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
-			group by sed.item_code, sed.t_warehouse
-		""",
+	def add_transfered_raw_materials_in_items(self) -> None:
+		available_materials = get_available_materials(self.work_order)
+
+		wo_data = frappe.db.get_value(
+			"Work Order",
 			self.work_order,
+			["qty", "produced_qty", "material_transferred_for_manufacturing as trans_qty"],
 			as_dict=1,
 		)
 
-		materials_already_backflushed = frappe.db.sql(
-			"""
-			select
-				item_code, sed.s_warehouse as warehouse, sum(qty) as qty
-			from
-				`tabStock Entry` se, `tabStock Entry Detail` sed
-			where
-				se.name = sed.parent and se.docstatus=1
-				and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
-				and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
-			group by sed.item_code, sed.s_warehouse
-		""",
-			self.work_order,
-			as_dict=1,
-		)
+		for key, row in available_materials.items():
+			remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
+			if remaining_qty_to_produce <= 0:
+				continue
 
-		backflushed_materials = {}
-		for d in materials_already_backflushed:
-			backflushed_materials.setdefault(d.item_code, []).append({d.warehouse: d.qty})
+			qty = (flt(row.qty) * flt(self.fg_completed_qty)) / remaining_qty_to_produce
 
-		po_qty = frappe.db.sql(
-			"""select qty, produced_qty, material_transferred_for_manufacturing from
-			`tabWork Order` where name=%s""",
-			self.work_order,
-			as_dict=1,
-		)[0]
-
-		manufacturing_qty = flt(po_qty.qty) or 1
-		produced_qty = flt(po_qty.produced_qty)
-		trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
-
-		for item in transferred_materials:
-			qty = item.qty
-			item_code = item.original_item or item.item_code
-			req_items = frappe.get_all(
-				"Work Order Item",
-				filters={"parent": self.work_order, "item_code": item_code},
-				fields=["required_qty", "consumed_qty"],
-			)
-
-			req_qty = flt(req_items[0].required_qty) if req_items else flt(4)
-			req_qty_each = flt(req_qty / manufacturing_qty)
-			consumed_qty = flt(req_items[0].consumed_qty) if req_items else 0
-
-			if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
-				if qty >= req_qty:
-					qty = (req_qty / trans_qty) * flt(self.fg_completed_qty)
-				else:
-					qty = qty - consumed_qty
-
-				if self.purpose == "Manufacture":
-					# If Material Consumption is booked, must pull only remaining components to finish product
-					if consumed_qty != 0:
-						remaining_qty = consumed_qty - (produced_qty * req_qty_each)
-						exhaust_qty = req_qty_each * produced_qty
-						if remaining_qty > exhaust_qty:
-							if (remaining_qty / (req_qty_each * flt(self.fg_completed_qty))) >= 1:
-								qty = 0
-							else:
-								qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
-					else:
-						if self.flags.backflush_based_on == "Material Transferred for Manufacture":
-							qty = (item.qty / trans_qty) * flt(self.fg_completed_qty)
-						else:
-							qty = req_qty_each * flt(self.fg_completed_qty)
-
-			elif backflushed_materials.get(item.item_code):
-				precision = frappe.get_precision("Stock Entry Detail", "qty")
-				for d in backflushed_materials.get(item.item_code):
-					if d.get(item.warehouse) > 0:
-						if qty > req_qty:
-							qty = (
-								(flt(qty, precision) - flt(d.get(item.warehouse), precision))
-								/ (flt(trans_qty, precision) - flt(produced_qty, precision))
-							) * flt(self.fg_completed_qty)
-
-							d[item.warehouse] -= qty
-
+			item = row.item_details
 			if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")):
 				qty = frappe.utils.ceil(qty)
 
-			if qty > 0:
-				self.add_to_stock_entry_detail(
-					{
-						item.item_code: {
-							"from_warehouse": item.warehouse,
-							"to_warehouse": "",
-							"qty": qty,
-							"item_name": item.item_name,
-							"description": item.description,
-							"stock_uom": item.stock_uom,
-							"expense_account": item.expense_account,
-							"cost_center": item.buying_cost_center,
-							"original_item": item.original_item,
-						}
-					}
-				)
+			if row.batch_details:
+				for batch_no, batch_qty in row.batch_details.items():
+					if qty <= 0 or batch_qty <= 0:
+						continue
+
+					if batch_qty > qty:
+						batch_qty = qty
+
+					item.batch_no = batch_no
+					self.update_item_in_stock_entry_detail(row, item, batch_qty)
+
+					row.batch_details[batch_no] -= batch_qty
+					qty -= batch_qty
+			else:
+				self.update_item_in_stock_entry_detail(row, item, qty)
+
+	def update_item_in_stock_entry_detail(self, row, item, qty) -> None:
+		ste_item_details = {
+			"from_warehouse": item.warehouse,
+			"to_warehouse": "",
+			"qty": qty,
+			"item_name": item.item_name,
+			"batch_no": item.batch_no,
+			"description": item.description,
+			"stock_uom": item.stock_uom,
+			"expense_account": item.expense_account,
+			"cost_center": item.buying_cost_center,
+			"original_item": item.original_item,
+		}
+
+		if row.serial_nos:
+			serial_nos = row.serial_nos
+			if item.batch_no:
+				serial_nos = self.get_serial_nos_based_on_transferred_batch(item.batch_no, row.serial_nos)
+
+			serial_nos = serial_nos[0 : cint(qty)]
+			ste_item_details["serial_no"] = "\n".join(serial_nos)
+
+			# remove consumed serial nos from list
+			for sn in serial_nos:
+				row.serial_nos.remove(sn)
+
+		self.add_to_stock_entry_detail({item.item_code: ste_item_details})
+
+	@staticmethod
+	def get_serial_nos_based_on_transferred_batch(batch_no, serial_nos) -> list:
+		serial_nos = frappe.get_all(
+			"Serial No", filters={"batch_no": batch_no, "name": ("in", serial_nos)}, order_by="creation"
+		)
+
+		return [d.name for d in serial_nos]
 
 	def get_pending_raw_materials(self, backflush_based_on=None):
 		"""
@@ -2528,3 +2471,81 @@
 		)
 
 	return supplied_item_details
+
+
+def get_available_materials(work_order) -> dict:
+	data = get_stock_entry_data(work_order)
+
+	available_materials = {}
+	for row in data:
+		key = (row.item_code, row.warehouse)
+		if row.purpose != "Material Transfer for Manufacture":
+			key = (row.item_code, row.s_warehouse)
+
+		if key not in available_materials:
+			available_materials.setdefault(
+				key,
+				frappe._dict(
+					{"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []}
+				),
+			)
+
+		item_data = available_materials[key]
+
+		if row.purpose == "Material Transfer for Manufacture":
+			item_data.qty += row.qty
+			if row.batch_no:
+				item_data.batch_details[row.batch_no] += row.qty
+
+			if row.serial_no:
+				item_data.serial_nos.extend(get_serial_nos(row.serial_no))
+				item_data.serial_nos.sort()
+		else:
+			# Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture'
+
+			item_data.qty -= row.qty
+			if row.batch_no:
+				item_data.batch_details[row.batch_no] -= row.qty
+
+			if row.serial_no:
+				for serial_no in get_serial_nos(row.serial_no):
+					item_data.serial_nos.remove(serial_no)
+
+	return available_materials
+
+
+def get_stock_entry_data(work_order):
+	stock_entry = frappe.qb.DocType("Stock Entry")
+	stock_entry_detail = frappe.qb.DocType("Stock Entry Detail")
+
+	return (
+		frappe.qb.from_(stock_entry)
+		.from_(stock_entry_detail)
+		.select(
+			stock_entry_detail.item_name,
+			stock_entry_detail.original_item,
+			stock_entry_detail.item_code,
+			stock_entry_detail.qty,
+			(stock_entry_detail.t_warehouse).as_("warehouse"),
+			(stock_entry_detail.s_warehouse).as_("s_warehouse"),
+			stock_entry_detail.description,
+			stock_entry_detail.stock_uom,
+			stock_entry_detail.expense_account,
+			stock_entry_detail.cost_center,
+			stock_entry_detail.batch_no,
+			stock_entry_detail.serial_no,
+			stock_entry.purpose,
+		)
+		.where(
+			(stock_entry.name == stock_entry_detail.parent)
+			& (stock_entry.work_order == work_order)
+			& (stock_entry.docstatus == 1)
+			& (stock_entry_detail.s_warehouse.isnotnull())
+			& (
+				stock_entry.purpose.isin(
+					["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
+				)
+			)
+		)
+		.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
+	).run(as_dict=1)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 6d8fdaa..9fb3be5 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -499,7 +499,7 @@
 
 def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no):
 	outgoing_rate = frappe.db.sql(
-		"""SELECT abs(stock_value_difference / actual_qty)
+		"""SELECT CASE WHEN actual_qty = 0 THEN 0 ELSE abs(stock_value_difference / actual_qty) END
 		FROM `tabStock Ledger Entry`
 		WHERE voucher_type = %s and voucher_no = %s
 			and item_code = %s and voucher_detail_no = %s
diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv
index 743b294..a4bfb86 100644
--- a/erpnext/translations/ru.csv
+++ b/erpnext/translations/ru.csv
@@ -44,7 +44,7 @@
 Account,Аккаунт,
 Account Number,Номер аккаунта,
 Account Number {0} already used in account {1},"Номер счета {0}, уже использованный в учетной записи {1}",
-Account Pay Only,Счет Оплатить только,
+Account Pay Only,Только оплатить счет,
 Account Type,Тип учетной записи,
 Account Type for {0} must be {1},Тип счета для {0} должен быть {1},
 "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'","Баланс счета в Кредите, запрещена установка 'Баланс должен быть' как 'Дебет'",
@@ -117,7 +117,7 @@
 Add Items,Добавить продукты,
 Add Leads,Добавить лид,
 Add Multiple Tasks,Добавить несколько задач,
-Add Row,Добавить ряд,
+Add Row,Добавить строку,
 Add Sales Partners,Добавить партнеров по продажам,
 Add Serial No,Добавить серийный номер,
 Add Students,Добавить студентов,
@@ -692,7 +692,7 @@
 Creating Company and Importing Chart of Accounts,Создание компании и импорт плана счетов,
 Creating Fees,Создание сборов,
 Creating Payment Entries......,Создание платежных записей......,
-Creating Salary Slips...,Создание зарплатных листков...,
+Creating Salary Slips...,Создание зарплатных ведомостей...,
 Creating student groups,Создание групп студентов,
 Creating {0} Invoice,Создание {0} счета,
 Credit,Кредит,
@@ -995,7 +995,7 @@
 Expenses Included In Asset Valuation,"Расходы, включенные в оценку активов",
 Expenses Included In Valuation,"Затрат, включаемых в оценке",
 Expired Batches,Просроченные партии,
-Expires On,Годен до,
+Expires On,Актуален до,
 Expiring On,Срок действия,
 Expiry (In Days),Срок действия (в днях),
 Explore,Обзор,
@@ -1411,7 +1411,7 @@
 Lab Tests and Vital Signs,Лабораторные тесты и жизненные знаки,
 Lab result datetime cannot be before testing datetime,Лабораторный результат datetime не может быть до тестирования даты и времени,
 Lab testing datetime cannot be before collection datetime,Лабораторное тестирование datetime не может быть до даты сбора данных,
-Label,Ярлык,
+Label,Метка,
 Laboratory,Лаборатория,
 Language Name,Название языка,
 Large,Большой,
@@ -2874,7 +2874,7 @@
 Supplier Invoice Date cannot be greater than Posting Date,"Дата Поставщик Счет не может быть больше, чем Дата публикации",
 Supplier Invoice No,Поставщик Счет №,
 Supplier Invoice No exists in Purchase Invoice {0},Номер счета поставщика отсутствует в счете на покупку {0},
-Supplier Name,наименование поставщика,
+Supplier Name,Наименование поставщика,
 Supplier Part No,Деталь поставщика №,
 Supplier Quotation,Предложение поставщика,
 Supplier Scorecard,Оценочная карта поставщика,
@@ -3091,7 +3091,7 @@
 Total Payments,Всего платежей,
 Total Present,Итого Текущая,
 Total Qty,Общее количество,
-Total Quantity,Общая численность,
+Total Quantity,Общее количество,
 Total Revenue,Общий доход,
 Total Student,Всего учеников,
 Total Target,Всего целей,
@@ -3498,7 +3498,7 @@
 Postal Code,Почтовый индекс,
 Previous,Предыдущая,
 Provider,Поставщик,
-Read Only,Только чтения,
+Read Only,Только чтение,
 Recipient,Сторона-реципиент,
 Reviews,Отзывы,
 Sender,Отправитель,
@@ -3879,7 +3879,7 @@
 On Supplier Creation,Создание поставщика,
 On Customer Creation,Создание клиента,
 Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx,
-Only expired allocation can be cancelled,Только истекшее распределение может быть отменено,
+Only expired allocation can be cancelled,Отменить можно только просроченное распределение,
 Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия,
 Open,Открыт,
 Open Contact,Открытый контакт,
@@ -4046,7 +4046,7 @@
 Service Level Agreement has been changed to {0}.,Соглашение об уровне обслуживания изменено на {0}.,
 Service Level Agreement was reset.,Соглашение об уровне обслуживания было сброшено.,
 Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Соглашение об уровне обслуживания с типом объекта {0} и объектом {1} уже существует.,
-Set,Задать,
+Set,Комплект,
 Set Meta Tags,Установить метатеги,
 Set {0} in company {1},Установить {0} в компании {1},
 Setup,Настройки,
@@ -4059,7 +4059,7 @@
 Show Warehouse-wise Stock,Показать складской запас,
 Size,Размер,
 Something went wrong while evaluating the quiz.,Что-то пошло не так при оценке теста.,
-Sr,Sr,
+Sr,№,
 Start,Начать,
 Start Date cannot be before the current date,Дата начала не может быть раньше текущей даты,
 Start Time,Время начала,
@@ -4513,7 +4513,7 @@
 Accounting Period,Период учета,
 Period Name,Название периода,
 Closed Documents,Закрытые документы,
-Accounts Settings,Настройки аккаунта,
+Accounts Settings,Настройка счетов,
 Settings for Accounts,Настройки для счетов,
 Make Accounting Entry For Every Stock Movement,Создавать бухгалтерские проводки при каждом перемещении запасов,
 Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,"Пользователи с этой ролью могут замороживать счета, а также создавать / изменять бухгалтерские проводки замороженных счетов",
@@ -5084,8 +5084,8 @@
 Item Tax Rate,Ставка налогов на продукт,
 Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Налоговый Подробная таблица выбирается из мастера элемента в виде строки и хранится в этой области.\n Используется по налогам и сборам,
 Purchase Order Item,Заказ товара,
-Purchase Receipt Detail,Деталь квитанции о покупке,
-Item Weight Details,Деталь Вес Подробности,
+Purchase Receipt Detail,Сведения о квитанции о покупке,
+Item Weight Details,Сведения о весе товара,
 Weight Per Unit,Вес на единицу,
 Total Weight,Общий вес,
 Weight UOM,Вес Единица измерения,
@@ -5198,7 +5198,7 @@
 Contact List,Список контактов,
 Hidden list maintaining the list of contacts linked to Shareholder,"Скрытый список, поддерживающий список контактов, связанных с Акционером",
 Specify conditions to calculate shipping amount,Укажите условия для расчета суммы доставки,
-Shipping Rule Label,Название правила доставки,
+Shipping Rule Label,Метка правила доставки,
 example: Next Day Shipping,Пример: доставка на следующий день,
 Shipping Rule Type,Тип правила доставки,
 Shipping Account,Счет доставки,
@@ -5236,7 +5236,7 @@
 Billing Interval Count,Счет интервала фактурирования,
 "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days","Количество интервалов для поля интервалов, например, если Interval является «Days», а количество интервалов фактурирования - 3, счета-фактуры будут генерироваться каждые 3 дня",
 Payment Plan,Платежный план,
-Subscription Plan Detail,Деталь плана подписки,
+Subscription Plan Detail,Сведения о плана подписки,
 Plan,План,
 Subscription Settings,Настройки подписки,
 Grace Period,Льготный период,
@@ -5802,7 +5802,7 @@
 Skip User creation for new Student,Пропустить создание пользователя для нового студента,
 "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.","По умолчанию для каждого нового Студента создается новый Пользователь. Если этот параметр включен, при создании нового Студента новый Пользователь не создается.",
 Instructor Records to be created by,Записи инструкторов должны быть созданы,
-Employee Number,Общее число сотрудников,
+Employee Number,Номер сотрудника,
 Fee Category,Категория платы,
 Fee Component,Компонент платы,
 Fees Category,Категория плат,
@@ -6196,7 +6196,7 @@
 Occupancy Status,Статус занятости,
 Vacant,Вакантно,
 Occupied,Занято,
-Item Details,Детальная информация о товаре,
+Item Details,Детальная информация о продукте,
 UOM Conversion in Hours,Преобразование UOM в часы,
 Rate / UOM,Скорость / UOM,
 Change in Item,Изменение продукта,
@@ -6868,8 +6868,8 @@
 Create Separate Payment Entry Against Benefit Claim,Создать отдельную заявку на подачу заявки на получение пособия,
 Condition and Formula,Состояние и формула,
 Amount based on formula,Сумма на основе формулы,
-Formula,формула,
-Salary Detail,Заработная плата: Подробности,
+Formula,Формула,
+Salary Detail,Подробно об заработной плате,
 Component,Компонент,
 Do not include in total,Не включать в общей сложности,
 Default Amount,По умолчанию количество,
@@ -6891,7 +6891,7 @@
 Total Interest Amount,Общая сумма процентов,
 Total Loan Repayment,Общая сумма погашения кредита,
 net pay info,Чистая информация платить,
-Gross Pay - Total Deduction - Loan Repayment,Gross Pay - Итого Вычет - Погашение кредита,
+Gross Pay - Total Deduction - Loan Repayment,Валовая заработная плата - Общий вычет - Погашение кредита,
 Total in words,Всего в словах,
 Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.",
 Salary Component for timesheet based payroll.,Компонент заработной платы для расчета зарплаты на основе расписания.,
@@ -6961,7 +6961,7 @@
 Attendees,Присутствующие,
 Employee Emails,Электронные почты сотрудников,
 Training Event Employee,Обучение сотрудников Событие,
-Invited,приглашенный,
+Invited,Приглашенный,
 Feedback Submitted,Отзыв отправлен,
 Optional,Необязательный,
 Training Result Employee,Результат обучения сотрудника,
@@ -7185,7 +7185,7 @@
 Item to be manufactured or repacked,Продукт должен быть произведен или переупакован,
 Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья,
 Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации,
-Allow Alternative Item,Разрешить альтернативный элемент,
+Allow Alternative Item,Разрешить альтернативный продукт,
 Item UOM,Единиц продукта,
 Conversion Rate,Коэффициент конверсии,
 Rate Of Materials Based On,Оценить материалов на основе,
@@ -7600,7 +7600,7 @@
 Import Supplier Invoice,Импортная накладная поставщика,
 Invoice Series,Серия счетов,
 Upload XML Invoices,Загрузить XML-счета,
-Zip File,Zip-файл,
+Zip File,Zip файл,
 Import Invoices,Импорт счетов,
 Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.,"Нажмите кнопку «Импортировать счета-фактуры», когда файл zip прикреплен к документу. Любые ошибки, связанные с обработкой, будут отображаться в журнале ошибок.",
 Lower Deduction Certificate,Свидетельство о нижнем удержании,
@@ -7635,7 +7635,7 @@
 Served,Подается,
 Restaurant Reservation,Бронирование ресторанов,
 Waitlisted,Лист ожидания,
-No Show,Нет шоу,
+No Show,Не показывать,
 No of People,Нет людей,
 Reservation Time,Время резервирования,
 Reservation End Time,Время окончания бронирования,
@@ -7873,8 +7873,8 @@
 "If disable, 'In Words' field will not be visible in any transaction","Если отключить, &quot;В словах&quot; поле не будет видно в любой сделке",
 Item Classification,Продуктовая классификация,
 General Settings,Основные настройки,
-Item Group Name,Пункт Название группы,
-Parent Item Group,Родитель Пункт Группа,
+Item Group Name,Название группы продуктов,
+Parent Item Group,Родительская группа продукта,
 Item Group Defaults,Элемент группы по умолчанию,
 Item Tax,Налог на продукт,
 Check this if you want to show in website,"Проверьте это, если вы хотите показать в веб-сайт",
@@ -7971,13 +7971,13 @@
 Tariff Number,Тарифный номер,
 Delivery To,Доставка,
 MAT-DN-.YYYY.-,MAT-DN-.YYYY.-,
-Is Return,Является Вернуться,
+Is Return,Возврат,
 Issue Credit Note,Кредитная кредитная карта,
-Return Against Delivery Note,Вернуться На накладной,
-Customer's Purchase Order No,Клиентам Заказ Нет,
+Return Against Delivery Note,Возврат по накладной,
+Customer's Purchase Order No,Заказ клиента №,
 Billing Address Name,Название адреса для выставления счета,
 Required only for sample item.,Требуется только для образца пункта.,
-"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.",
+"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в Шаблоне налогов и сборов с продаж, выберите его и нажмите кнопку ниже.",
 In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной.,
 In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной.,
 Transporter Info,Информация для транспортировки,
@@ -7991,8 +7991,8 @@
 Excise Page Number,Количество Акцизный Страница,
 Instructions,Инструкции,
 From Warehouse,Со склада,
-Against Sales Order,По Сделке,
-Against Sales Order Item,По Продукту Сделки,
+Against Sales Order,По сделке,
+Against Sales Order Item,По позиции сделки,
 Against Sales Invoice,Повторная накладная,
 Against Sales Invoice Item,Счет на продажу продукта,
 Available Batch Qty at From Warehouse,Доступные Пакетная Кол-во на со склада,
@@ -8008,7 +8008,7 @@
 Lock,Заблокировано,
 Visited,Посещен,
 Order Information,запросить информацию,
-Contact Information,Контакты,
+Contact Information,Контактная информация,
 Email sent to,Письмо отправлено,
 Dispatch Information,Информация о доставке,
 Estimated Arrival,Ожидаемое прибытие,
@@ -8121,7 +8121,7 @@
 Alternative Item Name,Альтернативное название продукта,
 Attribute Name,Название атрибута,
 Numeric Values,Числовые значения,
-From Range,От хребта,
+From Range,Из диапазона,
 Increment,Приращение,
 To Range,В диапазоне,
 Item Attribute Values,Пункт значений атрибутов,
@@ -8143,7 +8143,7 @@
 Default Expense Account,Счет учета затрат по умолчанию,
 Sales Defaults,По умолчанию,
 Default Selling Cost Center,По умолчанию Продажа Стоимость центр,
-Item Manufacturer,Пункт Производитель,
+Item Manufacturer,Производитель товара,
 Item Price,Цена продукта,
 Packing Unit,Упаковочный блок,
 Quantity  that must be bought or sold per UOM,"Количество, которое необходимо купить или продать за UOM",
@@ -8177,7 +8177,7 @@
 Purchase Receipt Items,Покупка продуктов,
 Get Items From Purchase Receipts,Получить продукты из покупки.,
 Distribute Charges Based On,Распределите платежи на основе,
-Landed Cost Help,Земельные Стоимость Помощь,
+Landed Cost Help,Справка по стоимости доставки,
 Manufacturers used in Items,Производители использовали в пунктах,
 Limited to 12 characters,Ограничено до 12 символов,
 MAT-MR-.YYYY.-,МАТ-MR-.YYYY.-,
@@ -8186,13 +8186,13 @@
 % Ordered,% заказано,
 Terms and Conditions Content,Условия Содержимое,
 Quantity and Warehouse,Количество и Склад,
-Lead Time Date,Время и Дата Лида,
-Min Order Qty,Минимальный заказ Кол-во,
+Lead Time Date,Дата выполнения заказа,
+Min Order Qty,Минимальное количество для заказа,
 Packed Item,Упаковано,
 To Warehouse (Optional),На склад (Необязательно),
 Actual Batch Quantity,Фактическое количество партий,
 Prevdoc DocType,Prevdoc DocType,
-Parent Detail docname,Родитель Деталь DOCNAME,
+Parent Detail docname,Сведения о родителе docname,
 "Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Создаёт упаковочные листы к упаковкам для доставки. Содержит номер упаковки, перечень содержимого и вес.",
 Indicates that the package is a part of this delivery (Only Draft),"Указывает, что пакет является частью этой поставки (только проект)",
 MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-,
@@ -8353,7 +8353,7 @@
 Auto Material Request,Автоматический запрос материалов,
 Inter Warehouse Transfer Settings,Настройки передачи между складами,
 Freeze Stock Entries,Замораживание поступления запасов,
-Stock Frozen Upto,остатки заморожены до,
+Stock Frozen Upto,Остатки заморожены до,
 Batch Identification,Идентификация партии,
 Use Naming Series,Использовать серийный номер,
 Naming Series Prefix,Префикс Идентификации по Имени,
@@ -8372,7 +8372,7 @@
 Service Level,Уровень обслуживания,
 Response By,Ответ от,
 Response By Variance,Ответ по отклонениям,
-Ongoing,постоянный,
+Ongoing,Постоянный,
 Resolution By,Разрешение по,
 Resolution By Variance,Разрешение по отклонениям,
 Service Level Agreement Creation,Создание соглашения об уровне обслуживания,