Merge branch 'frappe:develop' into develop
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index d28c3a8..1451189 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -94,7 +94,7 @@
 
 		unlink_ref_doc_from_payment_entries(self)
 		unlink_ref_doc_from_salary_slip(self.name)
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		self.make_gl_entries(1)
 		self.update_advance_paid()
 		self.update_expense_claim()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index a3a7be2..a10a810 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -95,7 +95,7 @@
 		self.set_status()
 
 	def on_cancel(self):
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		self.make_gl_entries(cancel=1)
 		self.update_expense_claim()
 		self.update_outstanding_amounts()
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/__init__.py b/erpnext/accounts/doctype/payment_ledger_entry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_ledger_entry/__init__.py
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js
new file mode 100644
index 0000000..5a7be8e
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Payment Ledger Entry', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
new file mode 100644
index 0000000..d961076
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -0,0 +1,180 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "format:PLE-{YY}-{MM}-{######}",
+ "creation": "2022-05-09 19:35:03.334361",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "posting_date",
+  "company",
+  "account_type",
+  "account",
+  "party_type",
+  "party",
+  "due_date",
+  "cost_center",
+  "finance_book",
+  "voucher_type",
+  "voucher_no",
+  "against_voucher_type",
+  "against_voucher_no",
+  "amount",
+  "account_currency",
+  "amount_in_account_currency",
+  "delinked"
+ ],
+ "fields": [
+  {
+   "fieldname": "posting_date",
+   "fieldtype": "Date",
+   "label": "Posting Date"
+  },
+  {
+   "fieldname": "account_type",
+   "fieldtype": "Select",
+   "label": "Account Type",
+   "options": "Receivable\nPayable"
+  },
+  {
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "label": "Account",
+   "options": "Account"
+  },
+  {
+   "fieldname": "party_type",
+   "fieldtype": "Link",
+   "label": "Party Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "party",
+   "fieldtype": "Dynamic Link",
+   "label": "Party",
+   "options": "party_type"
+  },
+  {
+   "fieldname": "voucher_type",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Voucher Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "voucher_no",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Voucher No",
+   "options": "voucher_type"
+  },
+  {
+   "fieldname": "against_voucher_type",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Against Voucher Type",
+   "options": "DocType"
+  },
+  {
+   "fieldname": "against_voucher_no",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Against Voucher No",
+   "options": "against_voucher_type"
+  },
+  {
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Amount",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "account_currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency"
+  },
+  {
+   "fieldname": "amount_in_account_currency",
+   "fieldtype": "Currency",
+   "label": "Amount in Account Currency",
+   "options": "account_currency"
+  },
+  {
+   "default": "0",
+   "fieldname": "delinked",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "DeLinked"
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company"
+  },
+  {
+   "fieldname": "cost_center",
+   "fieldtype": "Link",
+   "label": "Cost Center",
+   "options": "Cost Center"
+  },
+  {
+   "fieldname": "due_date",
+   "fieldtype": "Date",
+   "label": "Due Date"
+  },
+  {
+   "fieldname": "finance_book",
+   "fieldtype": "Link",
+   "label": "Finance Book",
+   "options": "Finance Book"
+  }
+ ],
+ "in_create": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2022-05-19 18:04:44.609115",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Ledger Entry",
+ "naming_rule": "Expression",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Auditor",
+   "share": 1
+  }
+ ],
+ "search_fields": "voucher_no, against_voucher_no",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
new file mode 100644
index 0000000..43e19f4
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+
+class PaymentLedgerEntry(Document):
+	def validate_account(self):
+		valid_account = frappe.db.get_list(
+			"Account",
+			"name",
+			filters={"name": self.account, "account_type": self.account_type, "company": self.company},
+			ignore_permissions=True,
+		)
+		if not valid_account:
+			frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
+
+	def validate(self):
+		self.validate_account()
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
new file mode 100644
index 0000000..a71b19e
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
@@ -0,0 +1,408 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import nowdate
+
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+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.stock.doctype.item.test_item import create_item
+
+
+class TestPaymentLedgerEntry(FrappeTestCase):
+	def setUp(self):
+		self.ple = qb.DocType("Payment Ledger Entry")
+		self.create_company()
+		self.create_item()
+		self.create_customer()
+		self.clear_old_entries()
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def create_company(self):
+		company_name = "_Test Payment Ledger"
+		company = None
+		if frappe.db.exists("Company", company_name):
+			company = frappe.get_doc("Company", company_name)
+		else:
+			company = frappe.get_doc(
+				{
+					"doctype": "Company",
+					"company_name": company_name,
+					"country": "India",
+					"default_currency": "INR",
+					"create_chart_of_accounts_based_on": "Standard Template",
+					"chart_of_accounts": "Standard",
+				}
+			)
+			company = company.save()
+
+		self.company = company.name
+		self.cost_center = company.cost_center
+		self.warehouse = "All Warehouses - _PL"
+		self.income_account = "Sales - _PL"
+		self.expense_account = "Cost of Goods Sold - _PL"
+		self.debit_to = "Debtors - _PL"
+		self.creditors = "Creditors - _PL"
+
+		# create bank account
+		if frappe.db.exists("Account", "HDFC - _PL"):
+			self.bank = "HDFC - _PL"
+		else:
+			bank_acc = frappe.get_doc(
+				{
+					"doctype": "Account",
+					"account_name": "HDFC",
+					"parent_account": "Bank Accounts - _PL",
+					"company": self.company,
+				}
+			)
+			bank_acc.save()
+			self.bank = bank_acc.name
+
+	def create_item(self):
+		item_name = "_Test PL Item"
+		item = create_item(
+			item_code=item_name, is_stock_item=0, company=self.company, warehouse=self.warehouse
+		)
+		self.item = item if isinstance(item, str) else item.item_code
+
+	def create_customer(self):
+		name = "_Test PL Customer"
+		if frappe.db.exists("Customer", name):
+			self.customer = name
+		else:
+			customer = frappe.new_doc("Customer")
+			customer.customer_name = name
+			customer.type = "Individual"
+			customer.save()
+			self.customer = customer.name
+
+	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 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()
+
+	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_payment_against_invoice(self):
+		transaction_date = nowdate()
+		amount = 100
+		ple = self.ple
+
+		# full payment using PE
+		si1 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+		pe1 = get_payment_entry(si1.doctype, si1.name).save().submit()
+
+		pl_entries = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where((ple.against_voucher_type == si1.doctype) & (ple.against_voucher_no == si1.name))
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values = [
+			{
+				"voucher_type": si1.doctype,
+				"voucher_no": si1.name,
+				"against_voucher_type": si1.doctype,
+				"against_voucher_no": si1.name,
+				"amount": amount,
+				"delinked": 0,
+			},
+			{
+				"voucher_type": pe1.doctype,
+				"voucher_no": pe1.name,
+				"against_voucher_type": si1.doctype,
+				"against_voucher_no": si1.name,
+				"amount": -amount,
+				"delinked": 0,
+			},
+		]
+		self.assertEqual(pl_entries[0], expected_values[0])
+		self.assertEqual(pl_entries[1], expected_values[1])
+
+	def test_partial_payment_against_invoice(self):
+		ple = self.ple
+		transaction_date = nowdate()
+		amount = 100
+
+		# partial payment of invoice using PE
+		si2 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+		pe2 = get_payment_entry(si2.doctype, si2.name)
+		pe2.get("references")[0].allocated_amount = 50
+		pe2.get("references")[0].outstanding_amount = 50
+		pe2 = pe2.save().submit()
+
+		pl_entries = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where((ple.against_voucher_type == si2.doctype) & (ple.against_voucher_no == si2.name))
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values = [
+			{
+				"voucher_type": si2.doctype,
+				"voucher_no": si2.name,
+				"against_voucher_type": si2.doctype,
+				"against_voucher_no": si2.name,
+				"amount": amount,
+				"delinked": 0,
+			},
+			{
+				"voucher_type": pe2.doctype,
+				"voucher_no": pe2.name,
+				"against_voucher_type": si2.doctype,
+				"against_voucher_no": si2.name,
+				"amount": -50,
+				"delinked": 0,
+			},
+		]
+		self.assertEqual(pl_entries[0], expected_values[0])
+		self.assertEqual(pl_entries[1], expected_values[1])
+
+	def test_cr_note_against_invoice(self):
+		ple = self.ple
+		transaction_date = nowdate()
+		amount = 100
+
+		# reconcile against return invoice
+		si3 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+		cr_note1 = self.create_sales_invoice(
+			qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+		)
+		cr_note1.is_return = 1
+		cr_note1.return_against = si3.name
+		cr_note1 = cr_note1.save().submit()
+
+		pl_entries = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where((ple.against_voucher_type == si3.doctype) & (ple.against_voucher_no == si3.name))
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values = [
+			{
+				"voucher_type": si3.doctype,
+				"voucher_no": si3.name,
+				"against_voucher_type": si3.doctype,
+				"against_voucher_no": si3.name,
+				"amount": amount,
+				"delinked": 0,
+			},
+			{
+				"voucher_type": cr_note1.doctype,
+				"voucher_no": cr_note1.name,
+				"against_voucher_type": si3.doctype,
+				"against_voucher_no": si3.name,
+				"amount": -amount,
+				"delinked": 0,
+			},
+		]
+		self.assertEqual(pl_entries[0], expected_values[0])
+		self.assertEqual(pl_entries[1], expected_values[1])
+
+	def test_je_against_inv_and_note(self):
+		ple = self.ple
+		transaction_date = nowdate()
+		amount = 100
+
+		# reconcile against return invoice using JE
+		si4 = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
+		cr_note2 = self.create_sales_invoice(
+			qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
+		)
+		cr_note2.is_return = 1
+		cr_note2 = cr_note2.save().submit()
+		je1 = self.create_journal_entry(
+			self.debit_to, self.debit_to, amount, posting_date=transaction_date
+		)
+		je1.get("accounts")[0].party_type = je1.get("accounts")[1].party_type = "Customer"
+		je1.get("accounts")[0].party = je1.get("accounts")[1].party = self.customer
+		je1.get("accounts")[0].reference_type = cr_note2.doctype
+		je1.get("accounts")[0].reference_name = cr_note2.name
+		je1.get("accounts")[1].reference_type = si4.doctype
+		je1.get("accounts")[1].reference_name = si4.name
+		je1 = je1.save().submit()
+
+		pl_entries_for_invoice = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where((ple.against_voucher_type == si4.doctype) & (ple.against_voucher_no == si4.name))
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values = [
+			{
+				"voucher_type": si4.doctype,
+				"voucher_no": si4.name,
+				"against_voucher_type": si4.doctype,
+				"against_voucher_no": si4.name,
+				"amount": amount,
+				"delinked": 0,
+			},
+			{
+				"voucher_type": je1.doctype,
+				"voucher_no": je1.name,
+				"against_voucher_type": si4.doctype,
+				"against_voucher_no": si4.name,
+				"amount": -amount,
+				"delinked": 0,
+			},
+		]
+		self.assertEqual(pl_entries_for_invoice[0], expected_values[0])
+		self.assertEqual(pl_entries_for_invoice[1], expected_values[1])
+
+		pl_entries_for_crnote = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where(
+				(ple.against_voucher_type == cr_note2.doctype) & (ple.against_voucher_no == cr_note2.name)
+			)
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values = [
+			{
+				"voucher_type": cr_note2.doctype,
+				"voucher_no": cr_note2.name,
+				"against_voucher_type": cr_note2.doctype,
+				"against_voucher_no": cr_note2.name,
+				"amount": -amount,
+				"delinked": 0,
+			},
+			{
+				"voucher_type": je1.doctype,
+				"voucher_no": je1.name,
+				"against_voucher_type": cr_note2.doctype,
+				"against_voucher_no": cr_note2.name,
+				"amount": amount,
+				"delinked": 0,
+			},
+		]
+		self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
+		self.assertEqual(pl_entries_for_crnote[1], expected_values[1])
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 8e0e62d..3b938ea 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -78,6 +78,8 @@
 			expense_account="Cost of Goods Sold - TPC",
 			rate=400,
 			debit_to="Debtors - TPC",
+			currency="USD",
+			customer="_Test Customer USD",
 		)
 		create_sales_invoice(
 			company=company,
@@ -86,6 +88,8 @@
 			expense_account="Cost of Goods Sold - TPC",
 			rate=200,
 			debit_to="Debtors - TPC",
+			currency="USD",
+			customer="_Test Customer USD",
 		)
 
 		pcv = self.make_period_closing_voucher(submit=False)
@@ -119,14 +123,17 @@
 		surplus_account = create_account()
 		cost_center = create_cost_center("Test Cost Center 1")
 
-		create_sales_invoice(
+		si = create_sales_invoice(
 			company=company,
 			income_account="Sales - TPC",
 			expense_account="Cost of Goods Sold - TPC",
 			cost_center=cost_center,
 			rate=400,
 			debit_to="Debtors - TPC",
+			currency="USD",
+			customer="_Test Customer USD",
 		)
+
 		jv = make_journal_entry(
 			account1="Cash - TPC",
 			account2="Sales - TPC",
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 572410f..98f3420 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -102,7 +102,9 @@
 		});
 	},
 
-	before_save: function(frm) {
+	before_save: async function(frm) {
+		frappe.dom.freeze(__('Processing Sales! Please Wait...'));
+
 		frm.set_value("grand_total", 0);
 		frm.set_value("net_total", 0);
 		frm.set_value("total_quantity", 0);
@@ -112,17 +114,23 @@
 			row.expected_amount = row.opening_amount;
 		}
 
-		for (let row of frm.doc.pos_transactions) {
-			frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => {
-				frm.doc.grand_total += flt(doc.grand_total);
-				frm.doc.net_total += flt(doc.net_total);
-				frm.doc.total_quantity += flt(doc.total_qty);
-				refresh_payments(doc, frm);
-				refresh_taxes(doc, frm);
-				refresh_fields(frm);
-				set_html_data(frm);
-			});
+		const pos_inv_promises = frm.doc.pos_transactions.map(
+			row => frappe.db.get_doc("POS Invoice", row.pos_invoice)
+		);
+
+		const pos_invoices = await Promise.all(pos_inv_promises);
+
+		for (let doc of pos_invoices) {
+			frm.doc.grand_total += flt(doc.grand_total);
+			frm.doc.net_total += flt(doc.net_total);
+			frm.doc.total_quantity += flt(doc.total_qty);
+			refresh_payments(doc, frm);
+			refresh_taxes(doc, frm);
+			refresh_fields(frm);
+			set_html_data(frm);
 		}
+
+		frappe.dom.unfreeze();
 	}
 });
 
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 94246e1..9649f80 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -96,6 +96,7 @@
 			)
 
 	def on_cancel(self):
+		self.ignore_linked_doctypes = "Payment Ledger Entry"
 		# run on cancel method of selling controller
 		super(SalesInvoice, self).on_cancel()
 		if not self.is_return and self.loyalty_program:
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 4cf19b4..3bd0cd2 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -752,7 +752,7 @@
 			title="_Test Pricing Rule with Min Qty - 2",
 		)
 
-		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
+		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
 		item = si.items[0]
 		item.stock_qty = 1
 		si.save()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a1d86e2..e6da666 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1418,7 +1418,12 @@
 		frappe.db.set(self, "status", "Cancelled")
 
 		unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
+		self.ignore_linked_doctypes = (
+			"GL Entry",
+			"Stock Ledger Entry",
+			"Repost Item Valuation",
+			"Payment Ledger Entry",
+		)
 		self.update_advance_tax_references(cancel=1)
 
 	def update_project(self):
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
index b46d2e3..c36efb8 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
@@ -1,10 +1,12 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "creation": "2013-01-10 16:34:08",
  "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n    - This can be on **Net Total** (that is the sum of basic amount).\n    - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n    - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
  "doctype": "DocType",
  "document_type": "Setup",
+ "engine": "InnoDB",
  "field_order": [
   "title",
   "is_default",
@@ -74,7 +76,8 @@
  ],
  "icon": "fa fa-money",
  "idx": 1,
- "modified": "2019-11-25 13:05:26.220275",
+ "links": [],
+ "modified": "2022-05-16 16:15:29.059370",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Taxes and Charges Template",
@@ -103,6 +106,10 @@
    "role": "Purchase User"
   }
  ],
+ "show_title_field_in_link": 1,
+ "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "title",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index e30289a..9dde85f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -861,27 +861,44 @@
 
 	set_timesheet_data: function(frm, timesheets) {
 		frm.clear_table("timesheets")
-		timesheets.forEach(timesheet => {
+		timesheets.forEach(async (timesheet) => {
 			if (frm.doc.currency != timesheet.currency) {
-				frappe.call({
-					method: "erpnext.setup.utils.get_exchange_rate",
-					args: {
-						from_currency: timesheet.currency,
-						to_currency: frm.doc.currency
-					},
-					callback: function(r) {
-						if (r.message) {
-							exchange_rate = r.message;
-							frm.events.append_time_log(frm, timesheet, exchange_rate);
-						}
-					}
-				});
+				const exchange_rate = await frm.events.get_exchange_rate(
+					frm, timesheet.currency, frm.doc.currency
+				)
+				frm.events.append_time_log(frm, timesheet, exchange_rate)
 			} else {
 				frm.events.append_time_log(frm, timesheet, 1.0);
 			}
 		});
 	},
 
+	async get_exchange_rate(frm, from_currency, to_currency) {
+		if (
+			frm.exchange_rates
+			&& frm.exchange_rates[from_currency]
+			&& frm.exchange_rates[from_currency][to_currency]
+		) {
+			return frm.exchange_rates[from_currency][to_currency];
+		}
+
+		return frappe.call({
+			method: "erpnext.setup.utils.get_exchange_rate",
+			args: {
+				from_currency,
+				to_currency
+			},
+			callback: function(r) {
+				if (r.message) {
+					// cache exchange rates
+					frm.exchange_rates = frm.exchange_rates || {};
+					frm.exchange_rates[from_currency] = frm.exchange_rates[from_currency] || {};
+					frm.exchange_rates[from_currency][to_currency] = r.message;
+				}
+			}
+		});
+	},
+
 	append_time_log: function(frm, time_log, exchange_rate) {
 		const row = frm.add_child("timesheets");
 		row.activity_type = time_log.activity_type;
@@ -892,7 +909,7 @@
 		row.billing_hours = time_log.billing_hours;
 		row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
 		row.timesheet_detail = time_log.name;
-    row.project_name = time_log.project_name;
+		row.project_name = time_log.project_name;
 
 		frm.refresh_field("timesheets");
 		frm.trigger("calculate_timesheet_totals");
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f0880c1..a580d45 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -396,7 +396,12 @@
 		unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
 
 		self.unlink_sales_invoice_from_timesheets()
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
+		self.ignore_linked_doctypes = (
+			"GL Entry",
+			"Stock Ledger Entry",
+			"Repost Item Valuation",
+			"Payment Ledger Entry",
+		)
 
 	def update_status_updater_args(self):
 		if cint(self.update_stock):
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
index 19781bd..408ecbf 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "creation": "2013-01-10 16:34:09",
@@ -77,7 +78,8 @@
  ],
  "icon": "fa fa-money",
  "idx": 1,
- "modified": "2019-11-25 13:06:03.279099",
+ "links": [],
+ "modified": "2022-05-16 16:14:52.061672",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Taxes and Charges Template",
@@ -113,7 +115,10 @@
    "write": 1
   }
  ],
+ "show_title_field_in_link": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
+ "title_field": "title",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1598d91..b0513f1 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -14,6 +14,7 @@
 	get_accounting_dimensions,
 )
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
+from erpnext.accounts.utils import create_payment_ledger_entry
 
 
 class ClosedAccountingPeriod(frappe.ValidationError):
@@ -34,6 +35,7 @@
 			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)
 				save_entries(gl_map, adv_adj, update_outstanding, from_repost)
 			# Post GL Map proccess there may no be any GL Entries
 			elif gl_map:
@@ -479,6 +481,7 @@
 		).run(as_dict=1)
 
 	if gl_entries:
+		create_payment_ledger_entry(gl_entries, cancel=1)
 		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/party.py b/erpnext/accounts/party.py
index db741d9..f4a44bd 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -897,3 +897,18 @@
 			return None
 	else:
 		return None
+
+
+def add_party_account(party_type, party, company, account):
+	doc = frappe.get_doc(party_type, party)
+	account_exists = False
+	for d in doc.get("accounts"):
+		if d.account == account:
+			account_exists = True
+
+	if not account_exists:
+		accounts = {"company": company, "account": account}
+
+		doc.append("accounts", accounts)
+
+		doc.save()
diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
index f3ccc86..c41d0d1 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
@@ -198,10 +198,12 @@
 			amount_field = (loan_doc.disbursed_amount).as_("credit")
 			posting_date = (loan_doc.disbursement_date).as_("posting_date")
 			account = loan_doc.disbursement_account
+			salary_condition = loan_doc.docstatus == 1
 		else:
 			amount_field = (loan_doc.amount_paid).as_("debit")
 			posting_date = (loan_doc.posting_date).as_("posting_date")
 			account = loan_doc.payment_account
+			salary_condition = loan_doc.repay_from_salary == 0
 
 		query = (
 			frappe.qb.from_(loan_doc)
@@ -214,14 +216,12 @@
 				posting_date,
 			)
 			.where(loan_doc.docstatus == 1)
+			.where(salary_condition)
 			.where(account == filters.get("account"))
 			.where(posting_date <= getdate(filters.get("report_date")))
 			.where(ifnull(loan_doc.clearance_date, "4000-01-01") > getdate(filters.get("report_date")))
 		)
 
-		if doctype == "Loan Repayment":
-			query.where(loan_doc.repay_from_salary == 0)
-
 		entries = query.run(as_dict=1)
 		loan_docs.extend(entries)
 
@@ -267,15 +267,17 @@
 			amount_field = Sum(loan_doc.disbursed_amount)
 			posting_date = (loan_doc.disbursement_date).as_("posting_date")
 			account = loan_doc.disbursement_account
+			salary_condition = loan_doc.docstatus == 1
 		else:
 			amount_field = Sum(loan_doc.amount_paid)
 			posting_date = (loan_doc.posting_date).as_("posting_date")
 			account = loan_doc.payment_account
-
+			salary_condition = loan_doc.repay_from_salary == 0
 		amount = (
 			frappe.qb.from_(loan_doc)
 			.select(amount_field)
 			.where(loan_doc.docstatus == 1)
+			.where(salary_condition)
 			.where(account == filters.get("account"))
 			.where(posting_date > getdate(filters.get("report_date")))
 			.where(ifnull(loan_doc.clearance_date, "4000-01-01") <= getdate(filters.get("report_date")))
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index 74926b9..75e983a 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -262,7 +262,10 @@
 def get_chart_data(columns, data):
 	labels = [d.get("label") for d in columns[2:]]
 	datasets = [
-		{"name": account.get("account").replace("'", ""), "values": [account.get("total")]}
+		{
+			"name": account.get("account").replace("'", ""),
+			"values": [account.get(d.get("fieldname")) for d in columns[2:]],
+		}
 		for account in data
 		if account.get("parent_account") == None and account.get("currency")
 	]
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 405922e..1869cc7 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -7,7 +7,7 @@
 
 import frappe
 import frappe.defaults
-from frappe import _, throw
+from frappe import _, qb, throw
 from frappe.model.meta import get_field_precision
 from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
 
@@ -15,6 +15,7 @@
 
 # imported to enable erpnext.accounts.utils.get_account_currency
 from erpnext.accounts.doctype.account.account import get_account_currency  # noqa
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
 from erpnext.stock import get_warehouse_account_map
 from erpnext.stock.utils import get_stock_value_on
 
@@ -1345,3 +1346,102 @@
 	if icons:
 		for icon in icons:
 			frappe.delete_doc("Desktop Icon", icon)
+
+
+def create_payment_ledger_entry(gl_entries, cancel=0):
+	if gl_entries:
+		ple = None
+
+		# companies
+		account = qb.DocType("Account")
+		companies = list(set([x.company for x in gl_entries]))
+
+		# receivable/payable account
+		accounts_with_types = (
+			qb.from_(account)
+			.select(account.name, account.account_type)
+			.where(
+				(account.account_type.isin(["Receivable", "Payable"]) & (account.company.isin(companies)))
+			)
+			.run(as_dict=True)
+		)
+		receivable_or_payable_accounts = [y.name for y in accounts_with_types]
+
+		def get_account_type(account):
+			for entry in accounts_with_types:
+				if entry.name == account:
+					return entry.account_type
+
+		dr_or_cr = 0
+		account_type = None
+		for gle in gl_entries:
+			if gle.account in receivable_or_payable_accounts:
+				account_type = get_account_type(gle.account)
+				if account_type == "Receivable":
+					dr_or_cr = gle.debit - gle.credit
+					dr_or_cr_account_currency = gle.debit_in_account_currency - gle.credit_in_account_currency
+				elif account_type == "Payable":
+					dr_or_cr = gle.credit - gle.debit
+					dr_or_cr_account_currency = gle.credit_in_account_currency - gle.debit_in_account_currency
+
+				if cancel:
+					dr_or_cr *= -1
+					dr_or_cr_account_currency *= -1
+
+				ple = frappe.get_doc(
+					{
+						"doctype": "Payment Ledger Entry",
+						"posting_date": gle.posting_date,
+						"company": gle.company,
+						"account_type": account_type,
+						"account": gle.account,
+						"party_type": gle.party_type,
+						"party": gle.party,
+						"cost_center": gle.cost_center,
+						"finance_book": gle.finance_book,
+						"due_date": gle.due_date,
+						"voucher_type": gle.voucher_type,
+						"voucher_no": gle.voucher_no,
+						"against_voucher_type": gle.against_voucher_type
+						if gle.against_voucher_type
+						else gle.voucher_type,
+						"against_voucher_no": gle.against_voucher if gle.against_voucher else gle.voucher_no,
+						"currency": gle.currency,
+						"amount": dr_or_cr,
+						"amount_in_account_currency": dr_or_cr_account_currency,
+						"delinked": True if cancel else False,
+					}
+				)
+
+				dimensions_and_defaults = get_dimensions()
+				if dimensions_and_defaults:
+					for dimension in dimensions_and_defaults[0]:
+						ple.set(dimension.fieldname, gle.get(dimension.fieldname))
+
+				if cancel:
+					delink_original_entry(ple)
+				ple.flags.ignore_permissions = 1
+				ple.submit()
+
+
+def delink_original_entry(pl_entry):
+	if pl_entry:
+		ple = qb.DocType("Payment Ledger Entry")
+		query = (
+			qb.update(ple)
+			.set(ple.delinked, True)
+			.set(ple.modified, now())
+			.set(ple.modified_by, frappe.session.user)
+			.where(
+				(ple.company == pl_entry.company)
+				& (ple.account_type == pl_entry.account_type)
+				& (ple.account == pl_entry.account)
+				& (ple.party_type == pl_entry.party_type)
+				& (ple.party == pl_entry.party)
+				& (ple.voucher_type == pl_entry.voucher_type)
+				& (ple.voucher_no == pl_entry.voucher_no)
+				& (ple.against_voucher_type == pl_entry.against_voucher_type)
+				& (ple.against_voucher_no == pl_entry.against_voucher_no)
+			)
+		)
+		query.run()
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 9189f18..44426ba 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -323,6 +323,7 @@
 		update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
 
 	def on_cancel(self):
+		self.ignore_linked_doctypes = "Payment Ledger Entry"
 		super(PurchaseOrder, self).on_cancel()
 
 		if self.is_against_so():
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c9c2ab1..056084b 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -34,6 +34,7 @@
 from erpnext.accounts.party import (
 	get_party_account,
 	get_party_account_currency,
+	get_party_gle_currency,
 	validate_party_frozen_disabled,
 )
 from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
@@ -168,6 +169,7 @@
 
 		self.validate_party()
 		self.validate_currency()
+		self.validate_party_account_currency()
 
 		if self.doctype in ["Purchase Invoice", "Sales Invoice"]:
 			pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
@@ -1447,6 +1449,27 @@
 				# at quotation / sales order level and we shouldn't stop someone
 				# from creating a sales invoice if sales order is already created
 
+	def validate_party_account_currency(self):
+		if self.doctype not in ("Sales Invoice", "Purchase Invoice"):
+			return
+
+		if self.is_opening == "Yes":
+			return
+
+		party_type, party = self.get_party()
+		party_gle_currency = get_party_gle_currency(party_type, party, self.company)
+		party_account = (
+			self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to")
+		)
+		party_account_currency = get_account_currency(party_account)
+
+		if not party_gle_currency and (party_account_currency != self.currency):
+			frappe.throw(
+				_("Party Account {0} currency and document currency should be same").format(
+					frappe.bold(party_account)
+				)
+			)
+
 	def delink_advance_entries(self, linked_doc_name):
 		total_allocated_amount = 0
 		for adv in self.advances:
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 813ac17..1c4bbbc 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -487,6 +487,7 @@
 
 accounting_dimension_doctypes = [
 	"GL Entry",
+	"Payment Ledger Entry",
 	"Sales Invoice",
 	"Purchase Invoice",
 	"Payment Entry",
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 89d86c1..589763c 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -105,7 +105,7 @@
 
 	def on_cancel(self):
 		self.update_task_and_project()
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		if self.payable_account:
 			self.make_gl_entries(cancel=True)
 
diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py
index c71407d..ce7caa3 100644
--- a/erpnext/hr/doctype/job_opening/job_opening.py
+++ b/erpnext/hr/doctype/job_opening/job_opening.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe import _
+from frappe.utils import get_link_to_form
 from frappe.website.website_generator import WebsiteGenerator
 
 from erpnext.hr.doctype.staffing_plan.staffing_plan import (
@@ -33,26 +34,32 @@
 				self.staffing_plan = staffing_plan[0].name
 				self.planned_vacancies = staffing_plan[0].vacancies
 		elif not self.planned_vacancies:
-			planned_vacancies = frappe.db.sql(
-				"""
-				select vacancies from `tabStaffing Plan Detail`
-				where parent=%s and designation=%s""",
-				(self.staffing_plan, self.designation),
+			self.planned_vacancies = frappe.db.get_value(
+				"Staffing Plan Detail",
+				{"parent": self.staffing_plan, "designation": self.designation},
+				"vacancies",
 			)
-			self.planned_vacancies = planned_vacancies[0][0] if planned_vacancies else None
 
 		if self.staffing_plan and self.planned_vacancies:
 			staffing_plan_company = frappe.db.get_value("Staffing Plan", self.staffing_plan, "company")
-			lft, rgt = frappe.get_cached_value("Company", staffing_plan_company, ["lft", "rgt"])
 
-			designation_counts = get_designation_counts(self.designation, self.company)
+			designation_counts = get_designation_counts(self.designation, self.company, self.name)
 			current_count = designation_counts["employee_count"] + designation_counts["job_openings"]
 
-			if self.planned_vacancies <= current_count:
+			number_of_positions = frappe.db.get_value(
+				"Staffing Plan Detail",
+				{"parent": self.staffing_plan, "designation": self.designation},
+				"number_of_positions",
+			)
+
+			if number_of_positions <= current_count:
 				frappe.throw(
 					_(
-						"Job Openings for designation {0} already open or hiring completed as per Staffing Plan {1}"
-					).format(self.designation, self.staffing_plan)
+						"Job Openings for the designation {0} are already open or the hiring is complete as per the Staffing Plan {1}"
+					).format(
+						frappe.bold(self.designation), get_link_to_form("Staffing Plan", self.staffing_plan)
+					),
+					title=_("Vacancies fulfilled"),
 				)
 
 	def get_context(self, context):
diff --git a/erpnext/hr/doctype/job_opening/test_job_opening.py b/erpnext/hr/doctype/job_opening/test_job_opening.py
index a72a6eb..e991054 100644
--- a/erpnext/hr/doctype/job_opening/test_job_opening.py
+++ b/erpnext/hr/doctype/job_opening/test_job_opening.py
@@ -3,8 +3,77 @@
 
 import unittest
 
-# test_records = frappe.get_test_records('Job Opening')
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, getdate
+
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company
 
 
-class TestJobOpening(unittest.TestCase):
-	pass
+class TestJobOpening(FrappeTestCase):
+	def setUp(self):
+		frappe.db.delete("Staffing Plan")
+		frappe.db.delete("Staffing Plan Detail")
+		frappe.db.delete("Job Opening")
+
+		make_company("_Test Opening Company", "_TOC")
+		frappe.db.delete("Employee", {"company": "_Test Opening Company"})
+
+	def test_vacancies_fulfilled(self):
+		make_employee(
+			"test_job_opening@example.com", company="_Test Opening Company", designation="Designer"
+		)
+
+		staffing_plan = frappe.get_doc(
+			{
+				"doctype": "Staffing Plan",
+				"company": "_Test Opening Company",
+				"name": "Test",
+				"from_date": getdate(),
+				"to_date": add_days(getdate(), 10),
+			}
+		)
+
+		staffing_plan.append(
+			"staffing_details",
+			{"designation": "Designer", "vacancies": 1, "estimated_cost_per_position": 50000},
+		)
+		staffing_plan.insert()
+		staffing_plan.submit()
+
+		self.assertEqual(staffing_plan.staffing_details[0].number_of_positions, 2)
+
+		# allows creating 1 job opening as per vacancy
+		opening_1 = get_job_opening()
+		opening_1.insert()
+
+		# vacancies as per staffing plan already fulfilled via job opening and existing employee count
+		opening_2 = get_job_opening(job_title="Designer New")
+		self.assertRaises(frappe.ValidationError, opening_2.insert)
+
+		# allows updating existing job opening
+		opening_1.status = "Closed"
+		opening_1.save()
+
+
+def get_job_opening(**args):
+	args = frappe._dict(args)
+
+	opening = frappe.db.exists("Job Opening", {"job_title": args.job_title or "Designer"})
+	if opening:
+		return frappe.get_doc("Job Opening", opening)
+
+	opening = frappe.get_doc(
+		{
+			"doctype": "Job Opening",
+			"job_title": "Designer",
+			"designation": "Designer",
+			"company": "_Test Opening Company",
+			"status": "Open",
+		}
+	)
+
+	opening.update(args)
+
+	return opening
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 0f655e3..7c0f0db 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -7,7 +7,7 @@
 from frappe.model.document import Document
 from frappe.utils import getdate, nowdate
 
-from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
+from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 from erpnext.hr.utils import set_employee_name, validate_active_employee
 from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import (
@@ -107,7 +107,10 @@
 		self.leave_balance = (
 			allocation.total_leaves_allocated
 			- allocation.carry_forwarded_leaves_count
-			- get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date)
+			# adding this because the function returns a -ve number
+			+ get_leaves_for_period(
+				self.employee, self.leave_type, allocation.from_date, self.encashment_date
+			)
 		)
 
 		encashable_days = self.leave_balance - frappe.db.get_value(
@@ -126,14 +129,25 @@
 		return True
 
 	def get_leave_allocation(self):
-		leave_allocation = frappe.db.sql(
-			"""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}'
-		between from_date and to_date and docstatus=1 and leave_type='{1}'
-		and employee= '{2}'""".format(
-				self.encashment_date or getdate(nowdate()), self.leave_type, self.employee
-			),
-			as_dict=1,
-		)  # nosec
+		date = self.encashment_date or getdate()
+
+		LeaveAllocation = frappe.qb.DocType("Leave Allocation")
+		leave_allocation = (
+			frappe.qb.from_(LeaveAllocation)
+			.select(
+				LeaveAllocation.name,
+				LeaveAllocation.from_date,
+				LeaveAllocation.to_date,
+				LeaveAllocation.total_leaves_allocated,
+				LeaveAllocation.carry_forwarded_leaves_count,
+			)
+			.where(
+				((LeaveAllocation.from_date <= date) & (date <= LeaveAllocation.to_date))
+				& (LeaveAllocation.docstatus == 1)
+				& (LeaveAllocation.leave_type == self.leave_type)
+				& (LeaveAllocation.employee == self.employee)
+			)
+		).run(as_dict=True)
 
 		return leave_allocation[0] if leave_allocation else None
 
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
index 83eb969..d06b6a3 100644
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py
@@ -4,26 +4,42 @@
 import unittest
 
 import frappe
-from frappe.utils import add_months, today
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, get_year_ending, get_year_start, getdate
 
 from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
 from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
 from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
 from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
 	create_assignment_for_multiple_employees,
 )
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+	make_holiday_list,
+	make_leave_application,
+)
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
-test_dependencies = ["Leave Type"]
+test_records = frappe.get_test_records("Leave Type")
 
 
-class TestLeaveEncashment(unittest.TestCase):
+class TestLeaveEncashment(FrappeTestCase):
 	def setUp(self):
-		frappe.db.sql("""delete from `tabLeave Period`""")
-		frappe.db.sql("""delete from `tabLeave Policy Assignment`""")
-		frappe.db.sql("""delete from `tabLeave Allocation`""")
-		frappe.db.sql("""delete from `tabLeave Ledger Entry`""")
-		frappe.db.sql("""delete from `tabAdditional Salary`""")
+		frappe.db.delete("Leave Period")
+		frappe.db.delete("Leave Policy Assignment")
+		frappe.db.delete("Leave Allocation")
+		frappe.db.delete("Leave Ledger Entry")
+		frappe.db.delete("Additional Salary")
+		frappe.db.delete("Leave Encashment")
+
+		if not frappe.db.exists("Leave Type", "_Test Leave Type Encashment"):
+			frappe.get_doc(test_records[2]).insert()
+
+		date = getdate()
+		year_start = getdate(get_year_start(date))
+		year_end = getdate(get_year_ending(date))
+
+		make_holiday_list("_Test Leave Encashment", year_start, year_end)
 
 		# create the leave policy
 		leave_policy = create_leave_policy(
@@ -32,9 +48,9 @@
 		leave_policy.submit()
 
 		# create employee, salary structure and assignment
-		self.employee = make_employee("test_employee_encashment@example.com")
+		self.employee = make_employee("test_employee_encashment@example.com", company="_Test Company")
 
-		self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3))
+		self.leave_period = create_leave_period(year_start, year_end, "_Test Company")
 
 		data = {
 			"assignment_based_on": "Leave Period",
@@ -53,27 +69,15 @@
 			other_details={"leave_encashment_amount_per_day": 50},
 		)
 
-	def tearDown(self):
-		for dt in [
-			"Leave Period",
-			"Leave Allocation",
-			"Leave Ledger Entry",
-			"Additional Salary",
-			"Leave Encashment",
-			"Salary Structure",
-			"Leave Policy",
-		]:
-			frappe.db.sql("delete from `tab%s`" % dt)
-
+	@set_holiday_list("_Test Leave Encashment", "_Test Company")
 	def test_leave_balance_value_and_amount(self):
-		frappe.db.sql("""delete from `tabLeave Encashment`""")
 		leave_encashment = frappe.get_doc(
 			dict(
 				doctype="Leave Encashment",
 				employee=self.employee,
 				leave_type="_Test Leave Type Encashment",
 				leave_period=self.leave_period.name,
-				payroll_date=today(),
+				encashment_date=self.leave_period.to_date,
 				currency="INR",
 			)
 		).insert()
@@ -88,15 +92,46 @@
 		add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
 		self.assertTrue(add_sal)
 
-	def test_creation_of_leave_ledger_entry_on_submit(self):
-		frappe.db.sql("""delete from `tabLeave Encashment`""")
+	@set_holiday_list("_Test Leave Encashment", "_Test Company")
+	def test_leave_balance_value_with_leaves_and_amount(self):
+		date = self.leave_period.from_date
+		leave_application = make_leave_application(
+			self.employee, date, add_days(date, 3), "_Test Leave Type Encashment"
+		)
+		leave_application.reload()
+
 		leave_encashment = frappe.get_doc(
 			dict(
 				doctype="Leave Encashment",
 				employee=self.employee,
 				leave_type="_Test Leave Type Encashment",
 				leave_period=self.leave_period.name,
-				payroll_date=today(),
+				encashment_date=self.leave_period.to_date,
+				currency="INR",
+			)
+		).insert()
+
+		self.assertEqual(leave_encashment.leave_balance, 10 - leave_application.total_leave_days)
+		# encashable days threshold is 5, total leaves are 6, so encashable days = 6-5 = 1
+		# with charge of 50 per day
+		self.assertEqual(leave_encashment.encashable_days, leave_encashment.leave_balance - 5)
+		self.assertEqual(leave_encashment.encashment_amount, 50)
+
+		leave_encashment.submit()
+
+		# assert links
+		add_sal = frappe.get_all("Additional Salary", filters={"ref_docname": leave_encashment.name})[0]
+		self.assertTrue(add_sal)
+
+	@set_holiday_list("_Test Leave Encashment", "_Test Company")
+	def test_creation_of_leave_ledger_entry_on_submit(self):
+		leave_encashment = frappe.get_doc(
+			dict(
+				doctype="Leave Encashment",
+				employee=self.employee,
+				leave_type="_Test Leave Type Encashment",
+				leave_period=self.leave_period.name,
+				encashment_date=self.leave_period.to_date,
 				currency="INR",
 			)
 		).insert()
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index ce7e50f..82472de 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -172,27 +172,24 @@
 
 
 @frappe.whitelist()
-def get_designation_counts(designation, company):
+def get_designation_counts(designation, company, job_opening=None):
 	if not designation:
 		return False
 
-	employee_counts = {}
 	company_set = get_descendants_of("Company", company)
 	company_set.append(company)
 
-	employee_counts["employee_count"] = frappe.db.get_value(
-		"Employee",
-		filters={"designation": designation, "status": "Active", "company": ("in", company_set)},
-		fieldname=["count(name)"],
+	employee_count = frappe.db.count(
+		"Employee", {"designation": designation, "status": "Active", "company": ("in", company_set)}
 	)
 
-	employee_counts["job_openings"] = frappe.db.get_value(
-		"Job Opening",
-		filters={"designation": designation, "status": "Open", "company": ("in", company_set)},
-		fieldname=["count(name)"],
-	)
+	filters = {"designation": designation, "status": "Open", "company": ("in", company_set)}
+	if job_opening:
+		filters["name"] = ("!=", job_opening)
 
-	return employee_counts
+	job_openings = frappe.db.count("Job Opening", filters)
+
+	return {"employee_count": employee_count, "job_openings": job_openings}
 
 
 @frappe.whitelist()
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
index a3adbbd..ac69c21 100644
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py
@@ -85,13 +85,16 @@
 	make_company()
 
 
-def make_company():
-	if frappe.db.exists("Company", "_Test Company 10"):
+def make_company(name=None, abbr=None):
+	if not name:
+		name = "_Test Company 10"
+
+	if frappe.db.exists("Company", name):
 		return
 
 	company = frappe.new_doc("Company")
-	company.company_name = "_Test Company 10"
-	company.abbr = "_TC10"
+	company.company_name = name
+	company.abbr = abbr or "_TC10"
 	company.parent_company = "_Test Company 3"
 	company.default_currency = "INR"
 	company.country = "Pakistan"
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 8614fcb..dcbdf8a 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -448,8 +448,6 @@
 					"remarks": remarks,
 					"cost_center": self.cost_center,
 					"posting_date": getdate(self.posting_date),
-					"party_type": self.applicant_type if self.repay_from_salary else "",
-					"party": self.applicant if self.repay_from_salary else "",
 				}
 			)
 		)
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 8a7634e..3d96f9c 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -499,15 +499,11 @@
 
 cur_frm.cscript.rate = function(doc, cdt, cdn) {
 	var d = locals[cdt][cdn];
-	var scrap_items = false;
-
-	if(cdt == 'BOM Scrap Item') {
-		scrap_items = true;
-	}
+	const is_scrap_item = cdt == "BOM Scrap Item";
 
 	if (d.bom_no) {
 		frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
-		get_bom_material_detail(doc, cdt, cdn, scrap_items);
+		get_bom_material_detail(doc, cdt, cdn, is_scrap_item);
 	} else {
 		erpnext.bom.calculate_rm_cost(doc);
 		erpnext.bom.calculate_scrap_materials_cost(doc);
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 3406215..0a8ae7b 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -33,7 +33,6 @@
   "amount",
   "base_amount",
   "section_break_18",
-  "scrap",
   "qty_consumed_per_unit",
   "section_break_27",
   "has_variants",
@@ -224,15 +223,6 @@
    "fieldtype": "Section Break"
   },
   {
-   "columns": 1,
-   "fieldname": "scrap",
-   "fieldtype": "Float",
-   "label": "Scrap %",
-   "oldfieldname": "scrap",
-   "oldfieldtype": "Currency",
-   "print_hide": 1
-  },
-  {
    "fieldname": "qty_consumed_per_unit",
    "fieldtype": "Float",
    "hidden": 1,
@@ -298,7 +288,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-01-24 16:57:57.020232",
+ "modified": "2022-05-19 02:32:43.785470",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index a98fc94..0a9fd8a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -42,6 +42,10 @@
 	pass
 
 
+class JobCardOverTransferError(frappe.ValidationError):
+	pass
+
+
 class JobCard(Document):
 	def onload(self):
 		excess_transfer = frappe.db.get_single_value(
@@ -522,23 +526,50 @@
 			},
 		)
 
-	def set_transferred_qty_in_job_card(self, ste_doc):
+	def set_transferred_qty_in_job_card_item(self, ste_doc):
+		from frappe.query_builder.functions import Sum
+
+		def _validate_over_transfer(row, transferred_qty):
+			"Block over transfer of items if not allowed in settings."
+			required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty")
+			is_excess = flt(transferred_qty) > flt(required_qty)
+			if is_excess:
+				frappe.throw(
+					_(
+						"Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}"
+					).format(
+						row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card
+					),
+					title=_("Excess Transfer"),
+					exc=JobCardOverTransferError,
+				)
+
 		for row in ste_doc.items:
 			if not row.job_card_item:
 				continue
 
-			qty = frappe.db.sql(
-				""" SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
-				WHERE  sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
-				se.purpose = 'Material Transfer for Manufacture'
-			""",
-				(row.job_card_item),
-			)[0][0]
+			sed = frappe.qb.DocType("Stock Entry Detail")
+			se = frappe.qb.DocType("Stock Entry")
+			transferred_qty = (
+				frappe.qb.from_(sed)
+				.join(se)
+				.on(sed.parent == se.name)
+				.select(Sum(sed.qty))
+				.where(
+					(sed.job_card_item == row.job_card_item)
+					& (se.docstatus == 1)
+					& (se.purpose == "Material Transfer for Manufacture")
+				)
+			).run()[0][0]
 
-			frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty))
+			allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
+			if not allow_excess:
+				_validate_over_transfer(row, transferred_qty)
+
+			frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
 
 	def set_transferred_qty(self, update_status=False):
-		"Set total FG Qty for which RM was transferred."
+		"Set total FG Qty in Job Card for which RM was transferred."
 		if not self.items:
 			self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
 
@@ -866,6 +897,7 @@
 		target.set("time_logs", [])
 		target.set("employee", [])
 		target.set("items", [])
+		target.set("sub_operations", [])
 		target.set_sub_operations()
 		target.get_required_items()
 		target.validate_time_logs()
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 4647ddf..7f3c7fe 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -1,15 +1,25 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import frappe
-from frappe.tests.utils import FrappeTestCase
-from frappe.utils import random_string
 
-from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError
+from typing import Literal
+
+import frappe
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import random_string
+from frappe.utils.data import add_to_date, now
+
+from erpnext.manufacturing.doctype.job_card.job_card import (
+	JobCardOverTransferError,
+	OperationMismatchError,
+	OverlapError,
+	make_corrective_job_card,
+)
 from erpnext.manufacturing.doctype.job_card.job_card import (
 	make_stock_entry as make_stock_entry_from_jc,
 )
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder
 from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 
@@ -17,34 +27,36 @@
 class TestJobCard(FrappeTestCase):
 	def setUp(self):
 		make_bom_for_jc_tests()
+		self.transfer_material_against: Literal["Work Order", "Job Card"] = "Work Order"
+		self.source_warehouse = None
+		self._work_order = None
 
-		transfer_material_against, source_warehouse = None, None
+	@property
+	def work_order(self) -> WorkOrder:
+		"""Work Order lazily created for tests."""
+		if not self._work_order:
+			self._work_order = make_wo_order_test_record(
+				item="_Test FG Item 2",
+				qty=2,
+				transfer_material_against=self.transfer_material_against,
+				source_warehouse=self.source_warehouse,
+			)
+		return self._work_order
 
-		tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
-		tests_that_transfer_against_jc = (
-			"test_job_card_multiple_materials_transfer",
-			"test_job_card_excess_material_transfer",
-			"test_job_card_partial_material_transfer",
-		)
-
-		if self._testMethodName in tests_that_skip_setup:
-			return
-
-		if self._testMethodName in tests_that_transfer_against_jc:
-			transfer_material_against = "Job Card"
-			source_warehouse = "Stores - _TC"
-
-		self.work_order = make_wo_order_test_record(
-			item="_Test FG Item 2",
-			qty=2,
-			transfer_material_against=transfer_material_against,
-			source_warehouse=source_warehouse,
-		)
+	def generate_required_stock(self, work_order: WorkOrder) -> None:
+		"""Create twice the stock for all required items in work order."""
+		for item in work_order.required_items:
+			make_stock_entry(
+				item_code=item.item_code,
+				target=item.source_warehouse or self.source_warehouse,
+				qty=item.required_qty * 2,
+				basic_rate=100,
+			)
 
 	def tearDown(self):
 		frappe.db.rollback()
 
-	def test_job_card(self):
+	def test_job_card_operations(self):
 
 		job_cards = frappe.get_all(
 			"Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
@@ -58,9 +70,6 @@
 			doc.operation_id = "Test Data"
 			self.assertRaises(OperationMismatchError, doc.save)
 
-		for d in job_cards:
-			frappe.delete_doc("Job Card", d.name)
-
 	def test_job_card_with_different_work_station(self):
 		job_cards = frappe.get_all(
 			"Job Card",
@@ -96,19 +105,11 @@
 			)
 			self.assertEqual(completed_qty, job_card.for_quantity)
 
-			doc.cancel()
-
-			for d in job_cards:
-				frappe.delete_doc("Job Card", d.name)
-
 	def test_job_card_overlap(self):
 		wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
 
-		jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
-		jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
-
-		jc1 = frappe.get_doc("Job Card", jc1_name)
-		jc2 = frappe.get_doc("Job Card", jc2_name)
+		jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
+		jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
 
 		employee = "_T-Employee-00001"  # from test records
 
@@ -137,10 +138,10 @@
 
 	def test_job_card_multiple_materials_transfer(self):
 		"Test transferring RMs separately against Job Card with multiple RMs."
-		make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
-		make_stock_entry(
-			item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
-		)
+		self.transfer_material_against = "Job Card"
+		self.source_warehouse = "Stores - _TC"
+
+		self.generate_required_stock(self.work_order)
 
 		job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
 		job_card = frappe.get_doc("Job Card", job_card_name)
@@ -165,16 +166,58 @@
 		# transfer was made for 2 fg qty in first transfer Stock Entry
 		self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
 
+	@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
 	def test_job_card_excess_material_transfer(self):
 		"Test transferring more than required RM against Job Card."
-		make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
-		make_stock_entry(
-			item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
+		self.transfer_material_against = "Job Card"
+		self.source_warehouse = "Stores - _TC"
+
+		self.generate_required_stock(self.work_order)
+
+		job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
+		self.assertEqual(job_card.status, "Open")
+
+		# fully transfer both RMs
+		transfer_entry_1 = make_stock_entry_from_jc(job_card.name)
+		transfer_entry_1.insert()
+		transfer_entry_1.submit()
+
+		# transfer extra qty of both RM due to previously damaged RM
+		transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
+		# deliberately change 'For Quantity'
+		transfer_entry_2.fg_completed_qty = 1
+		transfer_entry_2.items[0].qty = 5
+		transfer_entry_2.items[1].qty = 3
+		transfer_entry_2.insert()
+		transfer_entry_2.submit()
+
+		job_card.reload()
+		self.assertGreater(job_card.transferred_qty, job_card.for_quantity)
+
+		# Check if 'For Quantity' is negative
+		# as 'transferred_qty' > Qty to Manufacture
+		transfer_entry_3 = make_stock_entry_from_jc(job_card.name)
+		self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
+
+		job_card.append(
+			"time_logs",
+			{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 2},
 		)
+		job_card.save()
+		job_card.submit()
+
+		# JC is Completed with excess transfer
+		self.assertEqual(job_card.status, "Completed")
+
+	@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
+	def test_job_card_excess_material_transfer_block(self):
+
+		self.transfer_material_against = "Job Card"
+		self.source_warehouse = "Stores - _TC"
+
+		self.generate_required_stock(self.work_order)
 
 		job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
-		job_card = frappe.get_doc("Job Card", job_card_name)
-		self.assertEqual(job_card.status, "Open")
 
 		# fully transfer both RMs
 		transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
@@ -188,39 +231,19 @@
 		transfer_entry_2.items[0].qty = 5
 		transfer_entry_2.items[1].qty = 3
 		transfer_entry_2.insert()
-		transfer_entry_2.submit()
-
-		job_card.reload()
-		self.assertGreater(job_card.transferred_qty, job_card.for_quantity)
-
-		# Check if 'For Quantity' is negative
-		# as 'transferred_qty' > Qty to Manufacture
-		transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
-		self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
-
-		job_card.append(
-			"time_logs",
-			{"from_time": "2021-01-01 00:01:00", "to_time": "2021-01-01 06:00:00", "completed_qty": 2},
-		)
-		job_card.save()
-		job_card.submit()
-
-		# JC is Completed with excess transfer
-		self.assertEqual(job_card.status, "Completed")
+		self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit)
 
 	def test_job_card_partial_material_transfer(self):
 		"Test partial material transfer against Job Card"
+		self.transfer_material_against = "Job Card"
+		self.source_warehouse = "Stores - _TC"
 
-		make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
-		make_stock_entry(
-			item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
-		)
+		self.generate_required_stock(self.work_order)
 
-		job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
-		job_card = frappe.get_doc("Job Card", job_card_name)
+		job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
 
 		# partially transfer
-		transfer_entry = make_stock_entry_from_jc(job_card_name)
+		transfer_entry = make_stock_entry_from_jc(job_card.name)
 		transfer_entry.fg_completed_qty = 1
 		transfer_entry.get_items()
 		transfer_entry.insert()
@@ -232,7 +255,7 @@
 		self.assertEqual(transfer_entry.items[1].qty, 3)
 
 		# transfer remaining
-		transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
+		transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
 
 		self.assertEqual(transfer_entry_2.fg_completed_qty, 1)
 		self.assertEqual(transfer_entry_2.items[0].qty, 5)
@@ -277,7 +300,49 @@
 		self.assertEqual(transfer_entry.items[0].item_code, "_Test Item")
 		self.assertEqual(transfer_entry.items[0].qty, 2)
 
-		# rollback via tearDown method
+	@change_settings(
+		"Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1}
+	)
+	def test_corrective_costing(self):
+		job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
+
+		job_card.append(
+			"time_logs",
+			{"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2},
+		)
+		job_card.submit()
+
+		self.work_order.reload()
+		original_cost = self.work_order.total_operating_cost
+
+		# Create a corrective operation against it
+		corrective_action = frappe.get_doc(
+			doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
+		).insert()
+
+		corrective_job_card = make_corrective_job_card(
+			job_card.name, operation=corrective_action.name, for_operation=job_card.operation
+		)
+		corrective_job_card.hour_rate = 100
+		corrective_job_card.insert()
+		corrective_job_card.append(
+			"time_logs",
+			{
+				"from_time": add_to_date(now(), hours=2),
+				"to_time": add_to_date(now(), hours=2, minutes=30),
+				"completed_qty": 2,
+			},
+		)
+		corrective_job_card.submit()
+
+		self.work_order.reload()
+		cost_after_correction = self.work_order.total_operating_cost
+		self.assertGreater(cost_after_correction, original_cost)
+
+		corrective_job_card.cancel()
+		self.work_order.reload()
+		cost_after_cancel = self.work_order.total_operating_cost
+		self.assertEqual(cost_after_cancel, original_cost)
 
 
 def create_bom_with_multiple_operations():
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index ac2f61c..2aa31be 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -21,7 +21,7 @@
 	exploded_items = frappe.get_all(
 		"BOM Item",
 		filters={"parent": bom},
-		fields=["qty", "bom_no", "qty", "scrap", "item_code", "item_name", "description", "uom"],
+		fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
 	)
 
 	for item in exploded_items:
@@ -37,7 +37,6 @@
 				"qty": item.qty * qty,
 				"uom": item.uom,
 				"description": item.description,
-				"scrap": item.scrap,
 			}
 		)
 		if item.bom_no:
@@ -64,5 +63,4 @@
 			"fieldname": "description",
 			"width": 150,
 		},
-		{"label": _("Scrap"), "fieldtype": "data", "fieldname": "scrap", "width": 100},
 	]
diff --git a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
index 0a79130..c324172 100644
--- a/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
+++ b/erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
@@ -34,8 +34,8 @@
 		if filters.get(field):
 			query_filters[field] = ("in", filters.get(field))
 
-	query_filters["report_date"] = (">=", filters.get("from_date"))
-	query_filters["report_date"] = ("<=", filters.get("to_date"))
+	
+	query_filters["report_date"] = ["between", [filters.get("from_date"), filters.get("to_date")]]
 
 	return frappe.get_all(
 		"Quality Inspection", fields=fields, filters=query_filters, order_by="report_date asc"
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 4d9a7e0..8c0ebe7 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -359,7 +359,7 @@
 erpnext.patches.v13_0.update_accounts_in_loan_docs
 erpnext.patches.v14_0.update_batch_valuation_flag
 erpnext.patches.v14_0.delete_non_profit_doctypes
-erpnext.patches.v14_0.update_employee_advance_status
+erpnext.patches.v13_0.update_employee_advance_status
 erpnext.patches.v13_0.add_cost_center_in_loans
 erpnext.patches.v13_0.set_return_against_in_pos_invoice_references
 erpnext.patches.v13_0.remove_unknown_links_to_prod_plan_items # 24-03-2022
diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v13_0/update_employee_advance_status.py
similarity index 100%
rename from erpnext/patches/v14_0/update_employee_advance_status.py
rename to erpnext/patches/v13_0/update_employee_advance_status.py
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
new file mode 100644
index 0000000..c2267aa
--- /dev/null
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -0,0 +1,38 @@
+import frappe
+from frappe import qb
+
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+	get_dimensions,
+	make_dimension_in_accounting_doctypes,
+)
+from erpnext.accounts.utils import create_payment_ledger_entry
+
+
+def create_accounting_dimension_fields():
+	dimensions_and_defaults = get_dimensions()
+	if dimensions_and_defaults:
+		for dimension in dimensions_and_defaults[0]:
+			make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
+
+
+def execute():
+	# create accounting dimension fields in Payment Ledger
+	create_accounting_dimension_fields()
+
+	gl = qb.DocType("GL Entry")
+	accounts = frappe.db.get_list(
+		"Account", "name", filters={"account_type": ["in", ["Receivable", "Payable"]]}, as_list=True
+	)
+	gl_entries = []
+	if accounts:
+		# get all gl entries on receivable/payable accounts
+		gl_entries = (
+			qb.from_(gl)
+			.select("*")
+			.where(gl.account.isin(accounts))
+			.where(gl.is_cancelled == 0)
+			.run(as_dict=True)
+		)
+		if gl_entries:
+			# create payment ledger entries for the accounts receivable/payable
+			create_payment_ledger_entry(gl_entries, 0)
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json
index 1fd1cec..c540baf 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -76,9 +76,8 @@
    "fieldtype": "Select",
    "in_list_view": 1,
    "label": "Status",
-   "options": "Draft\nUnpaid\nPaid",
-   "read_only": 1,
-   "reqd": 1
+   "options": "Draft\nUnpaid\nPaid\nSubmitted\nCancelled",
+   "read_only": 1
   },
   {
    "depends_on": "eval: !doc.pay_via_salary_slip",
@@ -194,7 +193,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-02-02 14:00:45.536152",
+ "modified": "2022-05-27 13:56:14.349183",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Gratuity",
diff --git a/erpnext/payroll/doctype/gratuity/gratuity_list.js b/erpnext/payroll/doctype/gratuity/gratuity_list.js
new file mode 100644
index 0000000..20e3d5b
--- /dev/null
+++ b/erpnext/payroll/doctype/gratuity/gratuity_list.js
@@ -0,0 +1,12 @@
+frappe.listview_settings["Gratuity"] = {
+	get_indicator: function(doc) {
+		let status_color = {
+			"Draft": "red",
+			"Submitted": "blue",
+			"Cancelled": "red",
+			"Paid": "green",
+			"Unpaid": "orange",
+		};
+		return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
+	}
+};
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index aa03d80..1155a06 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -4,57 +4,69 @@
 import unittest
 
 import frappe
-from frappe.utils import add_days, flt, get_datetime, getdate
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, add_months, floor, flt, get_datetime, get_first_day, getdate
 
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
+from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
 from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip
 from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
 	make_deduction_salary_component,
 	make_earning_salary_component,
 	make_employee_salary_slip,
+	make_holiday_list,
 )
+from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
 from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
 
 test_dependencies = ["Salary Component", "Salary Slip", "Account"]
 
 
-class TestGratuity(unittest.TestCase):
+class TestGratuity(FrappeTestCase):
 	def setUp(self):
 		frappe.db.delete("Gratuity")
+		frappe.db.delete("Salary Slip")
 		frappe.db.delete("Additional Salary", {"ref_doctype": "Gratuity"})
 
 		make_earning_salary_component(
 			setup=True, test_tax=True, company_list=["_Test Company"], include_flexi_benefits=True
 		)
 		make_deduction_salary_component(setup=True, test_tax=True, company_list=["_Test Company"])
+		make_holiday_list()
 
+	@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
 	def test_get_last_salary_slip_should_return_none_for_new_employee(self):
 		new_employee = make_employee("new_employee@salary.com", company="_Test Company")
 		salary_slip = get_last_salary_slip(new_employee)
-		assert salary_slip is None
+		self.assertIsNone(salary_slip)
 
-	def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self):
-		employee, sal_slip = create_employee_and_get_last_salary_slip()
+	@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
+	def test_gratuity_based_on_current_slab_via_additional_salary(self):
+		"""
+		Range	|	Fraction
+		5-0		|	1
+		"""
+		doj = add_days(getdate(), -(6 * 365))
+		relieving_date = getdate()
+
+		employee = make_employee(
+			"test_employee_gratuity@salary.com",
+			company="_Test Company",
+			date_of_joining=doj,
+			relieving_date=relieving_date,
+		)
+		sal_slip = create_salary_slip("test_employee_gratuity@salary.com")
 
 		rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
 		gratuity = create_gratuity(pay_via_salary_slip=1, employee=employee, rule=rule.name)
 
 		# work experience calculation
-		date_of_joining, relieving_date = frappe.db.get_value(
-			"Employee", employee, ["date_of_joining", "relieving_date"]
-		)
-		employee_total_workings_days = (
-			get_datetime(relieving_date) - get_datetime(date_of_joining)
-		).days
+		employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days
+		experience = floor(employee_total_workings_days / rule.total_working_days_per_year)
+		self.assertEqual(gratuity.current_work_experience, experience)
 
-		experience = employee_total_workings_days / rule.total_working_days_per_year
-		gratuity.reload()
-		from math import floor
-
-		self.assertEqual(floor(experience), gratuity.current_work_experience)
-
-		# amount Calculation
+		# amount calculation
 		component_amount = frappe.get_all(
 			"Salary Detail",
 			filters={
@@ -64,20 +76,44 @@
 				"salary_component": "Basic Salary",
 			},
 			fields=["amount"],
+			limit=1,
 		)
-
-		""" 5 - 0 fraction is 1 """
-
 		gratuity_amount = component_amount[0].amount * experience
-		gratuity.reload()
-
 		self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
 
 		# additional salary creation (Pay via salary slip)
 		self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name}))
 
-	def test_check_gratuity_amount_based_on_all_previous_slabs(self):
-		employee, sal_slip = create_employee_and_get_last_salary_slip()
+		# gratuity should be marked "Paid" on the next salary slip submission
+		salary_slip = make_salary_slip("Test Gratuity", employee=employee)
+		salary_slip.posting_date = getdate()
+		salary_slip.insert()
+		salary_slip.submit()
+
+		gratuity.reload()
+		self.assertEqual(gratuity.status, "Paid")
+
+	@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
+	def test_gratuity_based_on_all_previous_slabs_via_payment_entry(self):
+		"""
+		Range	|	Fraction
+		0-1		|	0
+		1-5		|	0.7
+		5-0		|	1
+		"""
+		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+
+		doj = add_days(getdate(), -(6 * 365))
+		relieving_date = getdate()
+
+		employee = make_employee(
+			"test_employee_gratuity@salary.com",
+			company="_Test Company",
+			date_of_joining=doj,
+			relieving_date=relieving_date,
+		)
+
+		sal_slip = create_salary_slip("test_employee_gratuity@salary.com")
 		rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
 		set_mode_of_payment_account()
 
@@ -86,22 +122,11 @@
 		)
 
 		# work experience calculation
-		date_of_joining, relieving_date = frappe.db.get_value(
-			"Employee", employee, ["date_of_joining", "relieving_date"]
-		)
-		employee_total_workings_days = (
-			get_datetime(relieving_date) - get_datetime(date_of_joining)
-		).days
+		employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days
+		experience = floor(employee_total_workings_days / rule.total_working_days_per_year)
+		self.assertEqual(gratuity.current_work_experience, experience)
 
-		experience = employee_total_workings_days / rule.total_working_days_per_year
-
-		gratuity.reload()
-
-		from math import floor
-
-		self.assertEqual(floor(experience), gratuity.current_work_experience)
-
-		# amount Calculation
+		# amount calculation
 		component_amount = frappe.get_all(
 			"Salary Detail",
 			filters={
@@ -111,35 +136,22 @@
 				"salary_component": "Basic Salary",
 			},
 			fields=["amount"],
+			limit=1,
 		)
 
-		""" range  | Fraction
-			0-1    |    0
-			1-5    |   0.7
-			5-0    |    1
-		"""
-
 		gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount
-		gratuity.reload()
-
 		self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
 		self.assertEqual(gratuity.status, "Unpaid")
 
-		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+		pe = get_payment_entry("Gratuity", gratuity.name)
+		pe.reference_no = "123467"
+		pe.reference_date = getdate()
+		pe.submit()
 
-		pay_entry = get_payment_entry("Gratuity", gratuity.name)
-		pay_entry.reference_no = "123467"
-		pay_entry.reference_date = getdate()
-		pay_entry.save()
-		pay_entry.submit()
 		gratuity.reload()
-
 		self.assertEqual(gratuity.status, "Paid")
 		self.assertEqual(flt(gratuity.paid_amount, 2), flt(gratuity.amount, 2))
 
-	def tearDown(self):
-		frappe.db.rollback()
-
 
 def get_gratuity_rule(name):
 	rule = frappe.db.exists("Gratuity Rule", name)
@@ -149,7 +161,6 @@
 	rule.applicable_earnings_component = []
 	rule.append("applicable_earnings_component", {"salary_component": "Basic Salary"})
 	rule.save()
-	rule.reload()
 
 	return rule
 
@@ -204,23 +215,17 @@
 	).insert(ignore_permissions=True)
 
 
-def create_employee_and_get_last_salary_slip():
-	employee = make_employee("test_employee@salary.com", company="_Test Company")
-	frappe.db.set_value("Employee", employee, "relieving_date", getdate())
-	frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), -(6 * 365)))
+def create_salary_slip(employee):
 	if not frappe.db.exists("Salary Slip", {"employee": employee}):
-		salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly")
+		posting_date = get_first_day(add_months(getdate(), -1))
+		salary_slip = make_employee_salary_slip(
+			employee, "Monthly", "Test Gratuity", posting_date=posting_date
+		)
+		salary_slip.start_date = posting_date
+		salary_slip.end_date = None
 		salary_slip.submit()
 		salary_slip = salary_slip.name
 	else:
 		salary_slip = get_last_salary_slip(employee)
 
-	if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"):
-		from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
-
-		make_holiday_list()
-		frappe.db.set_value(
-			"Company", "_Test Company", "default_holiday_list", "Salary Slip Test Holiday List"
-		)
-
-	return employee, salary_slip
+	return salary_slip
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 54d56f9..473fb0d 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -16,6 +16,7 @@
 	comma_and,
 	date_diff,
 	flt,
+	get_link_to_form,
 	getdate,
 )
 
@@ -45,6 +46,7 @@
 
 	def before_submit(self):
 		self.validate_employee_details()
+		self.validate_payroll_payable_account()
 		if self.validate_attendance:
 			if self.validate_employee_attendance():
 				frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
@@ -66,6 +68,14 @@
 		if len(emp_with_sal_slip):
 			frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
 
+	def validate_payroll_payable_account(self):
+		if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"):
+			frappe.throw(
+				_(
+					"Account type cannot be set for payroll payable account {0}, please remove and try again"
+				).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account)))
+			)
+
 	def on_cancel(self):
 		frappe.delete_doc(
 			"Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 6a7f72b..f4f8415 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -116,10 +116,10 @@
 		self.update_payment_status_for_gratuity()
 
 	def update_payment_status_for_gratuity(self):
-		add_salary = frappe.db.get_all(
+		additional_salary = frappe.db.get_all(
 			"Additional Salary",
 			filters={
-				"payroll_date": ("BETWEEN", [self.start_date, self.end_date]),
+				"payroll_date": ("between", [self.start_date, self.end_date]),
 				"employee": self.employee,
 				"ref_doctype": "Gratuity",
 				"docstatus": 1,
@@ -128,10 +128,10 @@
 			limit=1,
 		)
 
-		if len(add_salary):
+		if additional_salary:
 			status = "Paid" if self.docstatus == 1 else "Unpaid"
-			if add_salary[0].name in [data.additional_salary for data in self.earnings]:
-				frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status)
+			if additional_salary[0].name in [entry.additional_salary for entry in self.earnings]:
+				frappe.db.set_value("Gratuity", additional_salary[0].ref_docname, "status", status)
 
 	def on_cancel(self):
 		self.set_status()
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 1bc3741..60ba2d9 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -997,7 +997,7 @@
 		return [no_of_days_in_month[1], no_of_holidays_in_month]
 
 
-def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
+def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, posting_date=None):
 	from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
 	if not salary_structure:
@@ -1008,7 +1008,11 @@
 	)
 
 	salary_structure_doc = make_salary_structure(
-		salary_structure, payroll_frequency, employee=employee.name, company=employee.company
+		salary_structure,
+		payroll_frequency,
+		employee=employee.name,
+		company=employee.company,
+		from_date=posting_date,
 	)
 	salary_slip_name = frappe.db.get_value(
 		"Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}
@@ -1018,7 +1022,7 @@
 		salary_slip = make_salary_slip(salary_structure_doc.name, employee=employee.name)
 		salary_slip.employee_name = employee.employee_name
 		salary_slip.payroll_frequency = payroll_frequency
-		salary_slip.posting_date = nowdate()
+		salary_slip.posting_date = posting_date or nowdate()
 		salary_slip.insert()
 	else:
 		salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 1cda0a0..1790da4 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -234,7 +234,7 @@
   },
   {
    "fieldname": "actual_start_date",
-   "fieldtype": "Data",
+   "fieldtype": "Date",
    "label": "Actual Start Date (via Time Sheet)",
    "read_only": 1
   },
@@ -458,7 +458,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 4,
- "modified": "2022-01-29 13:58:27.712714",
+ "modified": "2022-05-25 22:45:06.108499",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Project",
@@ -504,4 +504,4 @@
  "timeline_field": "customer",
  "title_field": "project_name",
  "track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 3dd11f6..16b0b4a 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -789,11 +789,23 @@
 		if(this.frm.doc.is_pos && (update_paid_amount===undefined || update_paid_amount)) {
 			$.each(this.frm.doc['payments'] || [], function(index, data) {
 				if(data.default && payment_status && total_amount_to_pay > 0) {
-					let base_amount = flt(total_amount_to_pay, precision("base_amount", data));
+					let base_amount, amount;
+
+					if (me.frm.doc.party_account_currency == me.frm.doc.currency) {
+						// if customer/supplier currency is same as company currency
+						// total_amount_to_pay is already in customer/supplier currency
+						// so base_amount has to be calculated using total_amount_to_pay
+						base_amount = flt(total_amount_to_pay * me.frm.doc.conversion_rate, precision("base_amount", data));
+						amount = flt(total_amount_to_pay, precision("amount", data));
+					} else {
+						base_amount = flt(total_amount_to_pay, precision("base_amount", data));
+						amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data));
+					}
+
 					frappe.model.set_value(data.doctype, data.name, "base_amount", base_amount);
-					let amount = flt(total_amount_to_pay / me.frm.doc.conversion_rate, precision("amount", data));
 					frappe.model.set_value(data.doctype, data.name, "amount", amount);
 					payment_status = false;
+
 				} else if(me.frm.doc.paid_amount) {
 					frappe.model.set_value(data.doctype, data.name, "amount", 0.0);
 				}
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 05a401b..d11205a 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -944,7 +944,11 @@
 		} else {
 			// company currency and doc currency is same
 			// this will prevent unnecessary conversion rate triggers
-			this.frm.set_value("conversion_rate", 1.0);
+			if(this.frm.doc.currency === this.get_company_currency()) {
+				this.frm.set_value("conversion_rate", 1.0);
+			} else {
+				this.conversion_rate();
+			}
 		}
 	}
 
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index ea56d07..ef24ce7 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -11,7 +11,7 @@
 
 			if (!invoice_eligible) return;
 
-			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
+			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, qrcode_image, __unsaved } = frm.doc;
 
 			const add_custom_button = (label, action) => {
 				if (!frm.custom_buttons[label]) {
@@ -149,84 +149,70 @@
 			}
 
 			if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
-				const fields = [
-					{
-						"label": "Reason",
-						"fieldname": "reason",
-						"fieldtype": "Select",
-						"reqd": 1,
-						"default": "1-Duplicate",
-						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
-					},
-					{
-						"label": "Remark",
-						"fieldname": "remark",
-						"fieldtype": "Data",
-						"reqd": 1
-					}
-				];
 				const action = () => {
-					const d = new frappe.ui.Dialog({
-						title: __('Cancel E-Way Bill'),
-						fields: fields,
-						primary_action: function() {
-							const data = d.get_values();
-							frappe.call({
-								method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
-								args: {
-									doctype,
-									docname: name,
-									eway_bill: ewaybill,
-									reason: data.reason.split('-')[0],
-									remark: data.remark
-								},
-								freeze: true,
-								callback: () => {
-									frappe.show_alert({
-										message: __('E-Way Bill Cancelled successfully'),
-										indicator: 'green'
-									}, 7);
-									frm.reload_doc();
-									d.hide();
-								},
-								error: () => {
-									frappe.show_alert({
-										message: __('E-Way Bill was not Cancelled'),
-										indicator: 'red'
-									}, 7);
-									d.hide();
-								}
-							});
-						},
-						primary_action_label: __('Submit')
-					});
-					d.show();
-				};
-				add_custom_button(__("Cancel E-Way Bill"), action);
-			}
+					let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
+					message += '<br><br>';
+					message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
 
-			if (irn && !irn_cancelled) {
-				const action = () => {
 					const dialog = frappe.msgprint({
-						title: __("Generate QRCode"),
-						message: __("Generate and attach QR Code using IRN?"),
+						title: __('Update E-Way Bill Cancelled Status?'),
+						message: message,
+						indicator: 'orange',
 						primary_action: {
 							action: function() {
 								frappe.call({
-									method: 'erpnext.regional.india.e_invoice.utils.generate_qrcode',
+									method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
 									args: { doctype, docname: name },
 									freeze: true,
-									callback: () => frm.reload_doc() || dialog.hide(),
-									error: () => dialog.hide()
+									callback: () => frm.reload_doc() && dialog.hide()
 								});
 							}
 						},
 						primary_action_label: __('Yes')
 					});
+				};
+				add_custom_button(__("Cancel E-Way Bill"), action);
+			}
+
+			if (irn && !irn_cancelled) {
+				let is_qrcode_attached = false;
+				if (qrcode_image && frm.attachments) {
+					let attachments = frm.attachments.get_attachments();
+					if (attachments.length != 0) {
+						for (let i = 0; i < attachments.length; i++) {
+							if (attachments[i].file_url == qrcode_image) {
+								is_qrcode_attached = true;
+								break;
+							}
+						}
+					}
+				}
+				if (!is_qrcode_attached) {
+					const action = () => {
+						if (frm.doc.__unsaved) {
+							frappe.throw(__('Please save the document to generate QRCode.'));
+						}
+						const dialog = frappe.msgprint({
+							title: __("Generate QRCode"),
+							message: __("Generate and attach QR Code using IRN?"),
+							primary_action: {
+								action: function() {
+									frappe.call({
+										method: 'erpnext.regional.india.e_invoice.utils.generate_qrcode',
+										args: { doctype, docname: name },
+										freeze: true,
+										callback: () => frm.reload_doc() || dialog.hide(),
+										error: () => dialog.hide()
+									});
+								}
+							},
+						primary_action_label: __('Yes')
+					});
 					dialog.show();
 				};
 				add_custom_button(__("Generate QRCode"), action);
 			}
+			}
 		}
 	});
 };
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index ed1002a..e5a1a59 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -649,6 +649,8 @@
 	try:
 		einvoice = safe_json_load(einvoice)
 		einvoice = santize_einvoice_fields(einvoice)
+	except json.JSONDecodeError:
+		raise
 	except Exception:
 		show_link_to_error_log(invoice, einvoice)
 
@@ -765,7 +767,9 @@
 		frappe.throw(
 			_(
 				"Error in input data. Please check for any special characters near following input: <br> {}"
-			).format(snippet)
+			).format(snippet),
+			title=_("Invalid JSON"),
+			exc=e,
 		)
 
 
@@ -797,7 +801,8 @@
 		self.irn_details_url = self.base_url + "/enriched/ei/api/invoice/irn"
 		self.generate_irn_url = self.base_url + "/enriched/ei/api/invoice"
 		self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin"
-		self.cancel_ewaybill_url = self.base_url + "/enriched/ei/api/ewayapi"
+		# cancel_ewaybill_url will only work if user have bought ewb api from adaequare.
+		self.cancel_ewaybill_url = self.base_url + "/enriched/ewb/ewayapi?action=CANEWB"
 		self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill"
 		self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image"
 
@@ -1005,13 +1010,32 @@
 		return failed
 
 	def fetch_and_attach_qrcode_from_irn(self):
-		qrcode = self.get_qrcode_from_irn(self.invoice.irn)
-		if qrcode:
-			qrcode_file = self.create_qr_code_file(qrcode)
-			frappe.db.set_value("Sales Invoice", self.invoice.name, "qrcode_image", qrcode_file.file_url)
-			frappe.msgprint(_("QR Code attached to the invoice"), alert=True)
+		is_qrcode_file_attached = self.invoice.qrcode_image and frappe.db.exists(
+			"File",
+			{
+				"attached_to_doctype": "Sales Invoice",
+				"attached_to_name": self.invoice.name,
+				"file_url": self.invoice.qrcode_image,
+				"attached_to_field": "qrcode_image",
+			},
+		)
+		if not is_qrcode_file_attached:
+			if self.invoice.signed_qr_code:
+				self.attach_qrcode_image()
+				frappe.db.set_value(
+					"Sales Invoice", self.invoice.name, "qrcode_image", self.invoice.qrcode_image
+				)
+				frappe.msgprint(_("QR Code attached to the invoice."), alert=True)
+			else:
+				qrcode = self.get_qrcode_from_irn(self.invoice.irn)
+				if qrcode:
+					qrcode_file = self.create_qr_code_file(qrcode)
+					frappe.db.set_value("Sales Invoice", self.invoice.name, "qrcode_image", qrcode_file.file_url)
+					frappe.msgprint(_("QR Code attached to the invoice."), alert=True)
+				else:
+					frappe.msgprint(_("QR Code not found for the IRN"), alert=True)
 		else:
-			frappe.msgprint(_("QR Code not found for the IRN"), alert=True)
+			frappe.msgprint(_("QR Code is already Attached"), indicator="green", alert=True)
 
 	def get_qrcode_from_irn(self, irn):
 		import requests
@@ -1185,6 +1209,7 @@
 		headers = self.get_headers()
 		data = json.dumps({"ewbNo": eway_bill, "cancelRsnCode": reason, "cancelRmrk": remark}, indent=4)
 		headers["username"] = headers["user_name"]
+		del headers["user_name"]
 		try:
 			res = self.make_request("post", self.cancel_ewaybill_url, headers, data)
 			if res.get("success"):
@@ -1275,7 +1300,6 @@
 
 	def attach_qrcode_image(self):
 		qrcode = self.invoice.signed_qr_code
-
 		qr_image = io.BytesIO()
 		url = qrcreate(qrcode, error="L")
 		url.png(qr_image, scale=2, quiet_zone=1)
@@ -1358,9 +1382,13 @@
 
 
 @frappe.whitelist()
-def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
-	gsp_connector = GSPConnector(doctype, docname)
-	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+def cancel_eway_bill(doctype, docname):
+	# NOTE: cancel_eway_bill api is disabled by Adequare.
+	# gsp_connector = GSPConnector(doctype, docname)
+	# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+
+	frappe.db.set_value(doctype, docname, "ewaybill", "")
+	frappe.db.set_value(doctype, docname, "eway_bill_cancelled", 1)
 
 
 @frappe.whitelist()
diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js
index 5f6dcde..88973e3 100644
--- a/erpnext/regional/india/taxes.js
+++ b/erpnext/regional/india/taxes.js
@@ -22,6 +22,7 @@
 				'shipping_address': frm.doc.shipping_address || '',
 				'shipping_address_name': frm.doc.shipping_address_name || '',
 				'customer_address': frm.doc.customer_address || '',
+				'company_address': frm.doc.company_address,
 				'supplier_address': frm.doc.supplier_address,
 				'customer': frm.doc.customer,
 				'supplier': frm.doc.supplier,
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index fd0fe26..0bdbe56 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -448,7 +448,7 @@
 					hsn_code = self.item_hsn_map.get(item_code)
 					tax_rate = 0
 					taxable_value = items.get(item_code)
-					for rates in hsn_wise_tax_rate.get(hsn_code):
+					for rates in hsn_wise_tax_rate.get(hsn_code, []):
 						if taxable_value > rates.get("minimum_taxable_value"):
 							tax_rate = rates.get("tax_rate")
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index b463213..7522e92 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -232,7 +232,7 @@
 			update_coupon_code_count(self.coupon_code, "used")
 
 	def on_cancel(self):
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		super(SalesOrder, self).on_cancel()
 
 		# Cannot cancel closed SO
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
index cb22fb6..91f4a5e 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -187,8 +187,9 @@
 		.on(soi.parent == so.name)
 		.join(ps)
 		.on(ps.parent == so.name)
+		.select(so.name)
+		.distinct()
 		.select(
-			so.name,
 			so.customer,
 			so.transaction_date.as_("submitted"),
 			ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
diff --git a/erpnext/setup/doctype/naming_series/naming_series.js b/erpnext/setup/doctype/naming_series/naming_series.js
index 861b2b3..0fb72ab 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.js
+++ b/erpnext/setup/doctype/naming_series/naming_series.js
@@ -54,5 +54,35 @@
 				frm.events.get_doc_and_prefix(frm);
 			}
 		});
-	}
+	},
+
+	naming_series_to_check(frm) {
+		frappe.call({
+			method: "preview_series",
+			doc: frm.doc,
+			callback: function(r) {
+				if (!r.exc) {
+					frm.set_value("preview", r.message);
+				} else {
+					frm.set_value("preview", __("Failed to generate preview of series"));
+				}
+			}
+		});
+	},
+
+	add_series(frm) {
+		const series = frm.doc.naming_series_to_check;
+
+		if (!series) {
+			frappe.show_alert(__("Please type a valid series."));
+			return;
+		}
+
+		if (!frm.doc.set_options.includes(series)) {
+			const current_series = frm.doc.set_options;
+			frm.set_value("set_options", `${current_series}\n${series}`);
+		} else {
+			frappe.show_alert(__("Series already added to transaction."));
+		}
+	},
 });
diff --git a/erpnext/setup/doctype/naming_series/naming_series.json b/erpnext/setup/doctype/naming_series/naming_series.json
index f936dcf..c65a6f0 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.json
+++ b/erpnext/setup/doctype/naming_series/naming_series.json
@@ -1,360 +1,132 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-01-25 11:35:08", 
- "custom": 0, 
- "description": "Set prefix for numbering series on your transactions", 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 0, 
+ "actions": [],
+ "creation": "2022-05-26 03:12:49.087648",
+ "description": "Set prefix for numbering series on your transactions",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "setup_series",
+  "select_doc_for_series",
+  "help_html",
+  "naming_series_to_check",
+  "preview",
+  "add_series",
+  "set_options",
+  "user_must_always_select",
+  "update",
+  "column_break_13",
+  "update_series",
+  "prefix",
+  "current_value",
+  "update_series_start"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Set prefix for numbering series on your transactions", 
-   "fieldname": "setup_series", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Setup Series", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "Set prefix for numbering series on your transactions",
+   "fieldname": "setup_series",
+   "fieldtype": "Section Break",
+   "label": "Setup Series"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "select_doc_for_series", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Select Transaction", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "select_doc_for_series",
+   "fieldtype": "Select",
+   "label": "Select Transaction"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "help_html", 
-   "fieldtype": "HTML", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Help HTML", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "<div class=\"well\">\nEdit list of Series in the box below. Rules:\n<ul>\n<li>Each Series Prefix on a new line.</li>\n<li>Allowed special characters are \"/\" and \"-\"</li>\n<li>Optionally, set the number of digits in the series using dot (.) followed by hashes (#). For example, \".####\" means that the series will have four digits. Default is five digits.</li>\n</ul>\nExamples:<br>\nINV-<br>\nINV-10-<br>\nINVK-<br>\nINV-.####<br>\n</div>", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "help_html",
+   "fieldtype": "HTML",
+   "label": "Help HTML",
+   "options": "<div class=\"well\">\n    Edit list of Series in the box below. Rules:\n    <ul>\n        <li>Each Series Prefix on a new line.</li>\n        <li>Allowed special characters are \"/\" and \"-\"</li>\n        <li>\n            Optionally, set the number of digits in the series using dot (.)\n            followed by hashes (#). For example, \".####\" means that the series\n            will have four digits. Default is five digits.\n        </li>\n        <li>\n            You can also use variables in the series name by putting them\n            between (.) dots\n            <br>\n            Support Variables:\n            <ul>\n                <li><code>.YYYY.</code> - Year in 4 digits</li>\n                <li><code>.YY.</code> - Year in 2 digits</li>\n                <li><code>.MM.</code> - Month</li>\n                <li><code>.DD.</code> - Day of month</li>\n                <li><code>.WW.</code> - Week of the year</li>\n                <li><code>.FY.</code> - Fiscal Year</li>\n                <li>\n                    <code>.{fieldname}.</code> - fieldname on the document e.g.\n                    <code>branch</code>\n                </li>\n            </ul>\n        </li>\n    </ul>\n    Examples:\n    <ul>\n        <li>INV-</li>\n        <li>INV-10-</li>\n        <li>INVK-</li>\n        <li>INV-.YYYY.-.{branch}.-.MM.-.####</li>\n    </ul>\n</div>\n<br>\n"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "set_options", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Series List for this Transaction", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "set_options",
+   "fieldtype": "Text",
+   "label": "Series List for this Transaction"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "description": "Check this if you want to force the user to select a series before saving. There will be no default if you check this.", 
-   "fieldname": "user_must_always_select", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "User must always select", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "default": "0",
+   "depends_on": "select_doc_for_series",
+   "description": "Check this if you want to force the user to select a series before saving. There will be no default if you check this.",
+   "fieldname": "user_must_always_select",
+   "fieldtype": "Check",
+   "label": "User must always select"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "select_doc_for_series", 
-   "fieldname": "update", 
-   "fieldtype": "Button", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "select_doc_for_series",
+   "fieldname": "update",
+   "fieldtype": "Button",
+   "label": "Update"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "Change the starting / current sequence number of an existing series.", 
-   "fieldname": "update_series", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update Series", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "Change the starting / current sequence number of an existing series.",
+   "fieldname": "update_series",
+   "fieldtype": "Section Break",
+   "label": "Update Series"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "prefix", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Prefix", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "prefix",
+   "fieldtype": "Select",
+   "label": "Prefix"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "This is the number of the last created transaction with this prefix", 
-   "fieldname": "current_value", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Current Value", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "description": "This is the number of the last created transaction with this prefix",
+   "fieldname": "current_value",
+   "fieldtype": "Int",
+   "label": "Current Value"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "update_series_start", 
-   "fieldtype": "Button", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Update Series Number", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "update_series_start", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "update_series_start",
+   "fieldtype": "Button",
+   "label": "Update Series Number",
+   "options": "update_series_start"
+  },
+  {
+   "fieldname": "naming_series_to_check",
+   "fieldtype": "Data",
+   "label": "Try a naming Series"
+  },
+  {
+   "default": " ",
+   "fieldname": "preview",
+   "fieldtype": "Text",
+   "label": "Preview of generated names",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "add_series",
+   "fieldtype": "Button",
+   "label": "Add this Series"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 1, 
- "icon": "fa fa-sort-by-order", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 1, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-08-17 03:41:37.685910", 
- "modified_by": "Administrator", 
- "module": "Setup", 
- "name": "Naming Series", 
- "owner": "Administrator", 
+ ],
+ "hide_toolbar": 1,
+ "icon": "fa fa-sort-by-order",
+ "idx": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-05-26 06:06:42.109504",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Naming Series",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 0, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "System Manager",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 0, 
- "read_only": 1, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "read_only": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index 4fba776..eafc264 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -6,7 +6,7 @@
 from frappe import _, msgprint, throw
 from frappe.core.doctype.doctype.doctype import validate_series
 from frappe.model.document import Document
-from frappe.model.naming import parse_naming_series
+from frappe.model.naming import make_autoname, parse_naming_series
 from frappe.permissions import get_doctypes_with_read
 from frappe.utils import cint, cstr
 
@@ -206,6 +206,35 @@
 		prefix = parse_naming_series(parts)
 		return prefix
 
+	@frappe.whitelist()
+	def preview_series(self) -> str:
+		"""Preview what the naming series will generate."""
+
+		generated_names = []
+		series = self.naming_series_to_check
+		if not series:
+			return ""
+
+		try:
+			doc = self._fetch_last_doc_if_available()
+			for _count in range(3):
+				generated_names.append(make_autoname(series, doc=doc))
+		except Exception as e:
+			if frappe.message_log:
+				frappe.message_log.pop()
+			return _("Failed to generate names from the series") + f"\n{str(e)}"
+
+		# Explcitly rollback in case any changes were made to series table.
+		frappe.db.rollback()  # nosemgrep
+		return "\n".join(generated_names)
+
+	def _fetch_last_doc_if_available(self):
+		"""Fetch last doc for evaluating naming series with fields."""
+		try:
+			return frappe.get_last_doc(self.select_doc_for_series)
+		except Exception:
+			return None
+
 
 def set_by_naming_series(
 	doctype, fieldname, naming_series, hide_name_field=True, make_mandatory=1
diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.py b/erpnext/setup/doctype/naming_series/test_naming_series.py
new file mode 100644
index 0000000..fce663e
--- /dev/null
+++ b/erpnext/setup/doctype/naming_series/test_naming_series.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.setup.doctype.naming_series.naming_series import NamingSeries
+
+
+class TestNamingSeries(FrappeTestCase):
+	def setUp(self):
+		self.ns: NamingSeries = frappe.get_doc("Naming Series")
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def test_naming_preview(self):
+		self.ns.select_doc_for_series = "Sales Invoice"
+
+		self.ns.naming_series_to_check = "AXBZ.####"
+		serieses = self.ns.preview_series().split("\n")
+		self.assertEqual(["AXBZ0001", "AXBZ0002", "AXBZ0003"], serieses)
+
+		self.ns.naming_series_to_check = "AXBZ-.{currency}.-"
+		serieses = self.ns.preview_series().split("\n")
+
+	def test_get_transactions(self):
+
+		naming_info = self.ns.get_transactions()
+		self.assertIn("Sales Invoice", naming_info["transactions"])
+
+		existing_naming_series = frappe.get_meta("Sales Invoice").get_field("naming_series").options
+
+		for series in existing_naming_series.split("\n"):
+			self.assertIn(series, naming_info["prefixes"])
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index aac6cd3..559883f 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -86,20 +86,29 @@
 class Batch(Document):
 	def autoname(self):
 		"""Generate random ID for batch if not specified"""
-		if not self.batch_id:
-			create_new_batch, batch_number_series = frappe.db.get_value(
-				"Item", self.item, ["create_new_batch", "batch_number_series"]
-			)
 
-			if create_new_batch:
-				if batch_number_series:
-					self.batch_id = make_autoname(batch_number_series, doc=self)
-				elif batch_uses_naming_series():
-					self.batch_id = self.get_name_from_naming_series()
-				else:
-					self.batch_id = get_name_from_hash()
+		if self.batch_id:
+			self.name = self.batch_id
+			return
+
+		create_new_batch, batch_number_series = frappe.db.get_value(
+			"Item", self.item, ["create_new_batch", "batch_number_series"]
+		)
+
+		if not create_new_batch:
+			frappe.throw(_("Batch ID is mandatory"), frappe.MandatoryError)
+
+		while not self.batch_id:
+			if batch_number_series:
+				self.batch_id = make_autoname(batch_number_series, doc=self)
+			elif batch_uses_naming_series():
+				self.batch_id = self.get_name_from_naming_series()
 			else:
-				frappe.throw(_("Batch ID is mandatory"), frappe.MandatoryError)
+				self.batch_id = get_name_from_hash()
+
+			# User might have manually created a batch with next number
+			if frappe.db.exists("Batch", self.batch_id):
+				self.batch_id = None
 
 		self.name = self.batch_id
 
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index c76da62..3e470d4 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -11,6 +11,8 @@
 
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
 	create_stock_reconciliation,
@@ -27,7 +29,7 @@
 		)
 
 	@classmethod
-	def make_batch_item(cls, item_name):
+	def make_batch_item(cls, item_name=None):
 		from erpnext.stock.doctype.item.test_item import make_item
 
 		if not frappe.db.exists(item_name):
@@ -245,7 +247,7 @@
 		if not use_naming_series:
 			frappe.set_value("Stock Settings", "Stock Settings", "use_naming_series", 0)
 
-	def make_new_batch(self, item_name, batch_id=None, do_not_insert=0):
+	def make_new_batch(self, item_name=None, batch_id=None, do_not_insert=0):
 		batch = frappe.new_doc("Batch")
 		item = self.make_batch_item(item_name)
 		batch.item = item.name
@@ -407,6 +409,26 @@
 
 		self.assertEqual(getdate(batch.expiry_date), getdate(expiry_date))
 
+	def test_autocreation_of_batches(self):
+		"""
+		Test if auto created Serial No excludes existing serial numbers
+		"""
+		item_code = make_item(
+			properties={
+				"has_batch_no": 1,
+				"batch_number_series": "BATCHEXISTING.###",
+				"create_new_batch": 1,
+			}
+		).name
+
+		manually_created_batch = self.make_new_batch(item_code, batch_id="BATCHEXISTING001").name
+
+		pr_1 = make_purchase_receipt(item_code=item_code, qty=1, batch_no=manually_created_batch)
+		pr_2 = make_purchase_receipt(item_code=item_code, qty=1)
+
+		self.assertNotEqual(pr_1.items[0].batch_no, pr_2.items[0].batch_no)
+		self.assertEqual("BATCHEXISTING002", pr_2.items[0].batch_no)
+
 
 def create_batch(item_code, rate, create_item_price_for_batch):
 	pi = make_purchase_invoice(
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index c998629..2614a7f 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -23,7 +23,7 @@
 
 class MaterialRequest(BuyingController):
 	def get_feed(self):
-		return _("{0}: {1}").format(self.status, self.material_request_type)
+		return
 
 	def check_if_already_pulled(self):
 		pass
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index ce3bd56..7fbfa62 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1285,6 +1285,14 @@
 		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
 			make_purchase_invoice as create_purchase_invoice,
 		)
+		from erpnext.accounts.party import add_party_account
+
+		add_party_account(
+			"Supplier",
+			"_Test Supplier USD",
+			"_Test Company with perpetual inventory",
+			"_Test Payable USD - TCP1",
+		)
 
 		pi = create_purchase_invoice(
 			company="_Test Company with perpetual inventory",
@@ -1293,6 +1301,7 @@
 			expense_account="_Test Account Cost for Goods Sold - TCP1",
 			currency="USD",
 			conversion_rate=70,
+			supplier="_Test Supplier USD",
 		)
 
 		pr = create_purchase_receipt(pi.name)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 890ac47..f1df54d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -298,19 +298,17 @@
 				for_update=True,
 			)
 
-			for f in (
-				"uom",
-				"stock_uom",
-				"description",
-				"item_name",
-				"expense_account",
-				"cost_center",
-				"conversion_factor",
-			):
-				if f == "stock_uom" or not item.get(f):
-					item.set(f, item_details.get(f))
-				if f == "conversion_factor" and item.uom == item_details.get("stock_uom"):
-					item.set(f, item_details.get(f))
+			reset_fields = ("stock_uom", "item_name")
+			for field in reset_fields:
+				item.set(field, item_details.get(field))
+
+			update_fields = ("uom", "description", "expense_account", "cost_center", "conversion_factor")
+
+			for field in update_fields:
+				if not item.get(field):
+					item.set(field, item_details.get(field))
+				if field == "conversion_factor" and item.uom == item_details.get("stock_uom"):
+					item.set(field, item_details.get(field))
 
 			if not item.transfer_qty and item.qty:
 				item.transfer_qty = flt(
@@ -1141,7 +1139,7 @@
 		if self.job_card:
 			job_doc = frappe.get_doc("Job Card", self.job_card)
 			job_doc.set_transferred_qty(update_status=True)
-			job_doc.set_transferred_qty_in_job_card(self)
+			job_doc.set_transferred_qty_in_job_card_item(self)
 
 		if self.work_order:
 			pro_doc = frappe.get_doc("Work Order", self.work_order)
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 71baf9f..6f4c910 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -2,8 +2,6 @@
 # License: GNU General Public License v3. See license.txt
 
 
-import unittest
-
 import frappe
 from frappe.permissions import add_user_permission, remove_user_permission
 from frappe.tests.utils import FrappeTestCase, change_settings
@@ -12,6 +10,7 @@
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.stock.doctype.item.test_item import (
 	create_item,
+	make_item,
 	make_item_variant,
 	set_item_variant_settings,
 )
@@ -1443,6 +1442,21 @@
 		self.assertEqual(mapped_se.items[0].basic_rate, 100)
 		self.assertEqual(mapped_se.items[0].basic_amount, 200)
 
+	def test_stock_entry_item_details(self):
+		item = make_item()
+
+		se = make_stock_entry(
+			item_code=item.name, qty=1, to_warehouse="_Test Warehouse - _TC", do_not_submit=True
+		)
+
+		self.assertEqual(se.items[0].item_name, item.item_name)
+		se.items[0].item_name = "wat"
+		se.items[0].stock_uom = "Kg"
+		se.save()
+
+		self.assertEqual(se.items[0].item_name, item.item_name)
+		self.assertEqual(se.items[0].stock_uom, item.stock_uom)
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js
index 9243e1e..d69c624 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.js
+++ b/erpnext/stock/doctype/warehouse/warehouse.js
@@ -1,88 +1,97 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-
 frappe.ui.form.on("Warehouse", {
-	onload: function(frm) {
-		frm.set_query("default_in_transit_warehouse", function() {
+	setup: function (frm) {
+		frm.set_query("default_in_transit_warehouse", function (doc) {
 			return {
-				filters:{
-					'warehouse_type' : 'Transit',
-					'is_group': 0,
-					'company': frm.doc.company
-				}
+				filters: {
+					warehouse_type: "Transit",
+					is_group: 0,
+					company: doc.company,
+				},
+			};
+		});
+
+		frm.set_query("parent_warehouse", function () {
+			return {
+				filters: {
+					is_group: 1,
+				},
+			};
+		});
+
+		frm.set_query("account", function (doc) {
+			return {
+				filters: {
+					is_group: 0,
+					account_type: "Stock",
+					company: doc.company,
+				},
 			};
 		});
 	},
 
-	refresh: function(frm) {
-		frm.toggle_display('warehouse_name', frm.doc.__islocal);
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
+	refresh: function (frm) {
+		frm.toggle_display("warehouse_name", frm.doc.__islocal);
+		frm.toggle_display(
+			["address_html", "contact_html"],
+			!frm.doc.__islocal
+		);
 
-
-		if(!frm.doc.__islocal) {
+		if (!frm.doc.__islocal) {
 			frappe.contacts.render_address_and_contact(frm);
-
 		} else {
 			frappe.contacts.clear_address_and_contact(frm);
 		}
 
-		frm.add_custom_button(__("Stock Balance"), function() {
-			frappe.set_route("query-report", "Stock Balance", {"warehouse": frm.doc.name});
+		frm.add_custom_button(__("Stock Balance"), function () {
+			frappe.set_route("query-report", "Stock Balance", {
+				warehouse: frm.doc.name,
+			});
 		});
 
-		if (cint(frm.doc.is_group) == 1) {
-			frm.add_custom_button(__('Group to Non-Group'),
-				function() { convert_to_group_or_ledger(frm); }, 'fa fa-retweet', 'btn-default')
-		} else if (cint(frm.doc.is_group) == 0) {
-			if(frm.doc.__onload && frm.doc.__onload.account) {
-				frm.add_custom_button(__("General Ledger"), function() {
+		frm.add_custom_button(
+			frm.doc.is_group
+				? __("Convert to Ledger", null, "Warehouse")
+				: __("Convert to Group", null, "Warehouse"),
+			function () {
+				convert_to_group_or_ledger(frm);
+			},
+		);
+
+		if (!frm.doc.is_group && frm.doc.__onload && frm.doc.__onload.account) {
+			frm.add_custom_button(
+				__("General Ledger", null, "Warehouse"),
+				function () {
 					frappe.route_options = {
-						"account": frm.doc.__onload.account,
-						"company": frm.doc.company
-					}
+						account: frm.doc.__onload.account,
+						company: frm.doc.company,
+					};
 					frappe.set_route("query-report", "General Ledger");
-				});
-			}
-
-			frm.add_custom_button(__('Non-Group to Group'),
-				function() { convert_to_group_or_ledger(frm); }, 'fa fa-retweet', 'btn-default')
-		}
-
-		frm.toggle_enable(['is_group', 'company'], false);
-
-		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Warehouse'};
-
-		frm.fields_dict['parent_warehouse'].get_query = function(doc) {
-			return {
-				filters: {
-					"is_group": 1,
 				}
-			}
+			);
 		}
 
-		frm.fields_dict['account'].get_query = function(doc) {
-			return {
-				filters: {
-					"is_group": 0,
-					"account_type": "Stock",
-					"company": frm.doc.company
-				}
-			}
-		}
-	}
+		frm.toggle_enable(["is_group", "company"], false);
+
+		frappe.dynamic_link = {
+			doc: frm.doc,
+			fieldname: "name",
+			doctype: "Warehouse",
+		};
+	},
 });
 
-function convert_to_group_or_ledger(frm){
+function convert_to_group_or_ledger(frm) {
 	frappe.call({
-		method:"erpnext.stock.doctype.warehouse.warehouse.convert_to_group_or_ledger",
+		method: "erpnext.stock.doctype.warehouse.warehouse.convert_to_group_or_ledger",
 		args: {
 			docname: frm.doc.name,
-			is_group: frm.doc.is_group
+			is_group: frm.doc.is_group,
 		},
-		callback: function(){
+		callback: function () {
 			frm.refresh();
-		}
-
-	})
+		},
+	});
 }
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 324ff4f..c6241f8 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -199,7 +199,7 @@
 	if not args.get("price_list"):
 		args.price_list = args.get("selling_price_list") or args.get("buying_price_list")
 
-	if args.barcode:
+	if not args.item_code and args.barcode:
 		args.item_code = get_item_code(barcode=args.barcode)
 	elif not args.item_code and args.serial_no:
 		args.item_code = get_item_code(serial_no=args.serial_no)
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 4763b47..f19c75f 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -252,11 +252,14 @@
 	)
 
 	for exception in exceptions_list:
-		exception = json.loads(exception)
-		error_message = """<div class='small text-muted'>{0}</div><br>""".format(
-			_(exception.get("message"))
-		)
-		content += error_message
+		try:
+			exception = json.loads(exception)
+			error_message = """<div class='small text-muted'>{0}</div><br>""".format(
+				_(exception.get("message"))
+			)
+			content += error_message
+		except Exception:
+			pass
 
 	content += _("Regards,") + "<br>" + _("Administrator")
 
diff --git a/erpnext/templates/emails/request_for_quotation.html b/erpnext/templates/emails/request_for_quotation.html
index 3283987..5b073e6 100644
--- a/erpnext/templates/emails/request_for_quotation.html
+++ b/erpnext/templates/emails/request_for_quotation.html
@@ -1,24 +1,29 @@
 <h4>{{_("Request for Quotation")}}</h4>
 <p>{{ supplier_salutation if supplier_salutation else ''}} {{ supplier_name }},</p>
 <p>{{ message }}</p>
-
 <p>{{_("The Request for Quotation can be accessed by clicking on the following button")}}:</p>
-<p>
-	<button style="border: 1px solid #15c; padding: 6px; border-radius: 5px; background-color: white;">
-		<a href="{{ rfq_link }}" style="color: #15c; text-decoration:none;" target="_blank">Submit your Quotation</a>
-	</button>
-</p><br>
-
-<p>{{_("Regards")}},<br>
-{{ user_fullname }}</p><br>
-
+<br>
+<a
+	href="{{ rfq_link }}"
+	class="btn btn-default btn-sm"
+	target="_blank">
+	{{ _("Submit your Quotation") }}
+</a>
+<br>
+<br>
 {% if update_password_link %}
-
+<br>
 <p>{{_("Please click on the following button to set your new password")}}:</p>
-<p>
-	<button style="border: 1px solid #15c; padding: 4px; border-radius: 5px; background-color: white;">
-		<a href="{{ update_password_link }}" style="color: #15c; font-size: 12px; text-decoration:none;" target="_blank">{{_("Update Password") }}</a>
-	</button>
-</p>
-
+<a
+	href="{{ update_password_link }}"
+	class="btn btn-default btn-xs"
+	target="_blank">
+	{{_("Set Password") }}
+</a>
+<br>
+<br>
 {% endif %}
+<p>
+	{{_("Regards")}},<br>
+	{{ user_fullname }}
+</p>
diff --git a/erpnext/tests/test_search.py b/erpnext/tests/test_search.py
index ffe9a5a..3685828 100644
--- a/erpnext/tests/test_search.py
+++ b/erpnext/tests/test_search.py
@@ -8,6 +8,7 @@
 	# Search for the word "cond", part of the word "conduire" (Lead) in french.
 	def test_contact_search_in_foreign_language(self):
 		try:
+			frappe.local.lang_full_dict = None  # reset cached translations
 			frappe.local.lang = "fr"
 			output = filter_dynamic_link_doctypes(
 				"DocType", "cond", "name", 0, 20, {"fieldtype": "HTML", "fieldname": "contact_html"}
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index ccd613d..45bc6c2 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -783,7 +783,7 @@
 Default BOM ({0}) must be active for this item or its template,Standardstückliste ({0}) muss für diesen Artikel oder dessen Vorlage aktiv sein,
 Default BOM for {0} not found,Standardstückliste für {0} nicht gefunden,
 Default BOM not found for Item {0} and Project {1},Standard-Stückliste nicht gefunden für Position {0} und Projekt {1},
-Default In-Transit Warehouse, Standardlager für Waren im Transit,
+Default In-Transit Warehouse,Standard-Durchgangslager,
 Default Letter Head,Standardbriefkopf,
 Default Tax Template,Standardsteuervorlage,
 Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,"Die Standard-Maßeinheit für Artikel {0} kann nicht direkt geändert werden, weil Sie bereits einige Transaktionen mit einer anderen Maßeinheit durchgeführt haben. Sie müssen einen neuen Artikel erstellen, um eine andere Standard-Maßeinheit verwenden zukönnen.",
@@ -1178,7 +1178,7 @@
 Group by Voucher,Gruppieren nach Beleg,
 Group by Voucher (Consolidated),Gruppieren nach Beleg (konsolidiert),
 Group node warehouse is not allowed to select for transactions,Gruppenknoten Lager ist nicht für Transaktionen zu wählen erlaubt,
-Group to Non-Group,Gruppe an konzernfremde,
+Convert to Ledger,In Lagerbuch umwandeln,Warehouse
 Group your students in batches,Gruppieren Sie Ihre Schüler in den Reihen,
 Groups,Gruppen,
 Guardian1 Email ID,Guardian1 E-Mail-ID,
@@ -1701,7 +1701,7 @@
 No Remarks,Keine Anmerkungen,
 No Result to submit,Kein Ergebnis zur Einreichung,
 No Salary Structure assigned for Employee {0} on given date {1},Keine Gehaltsstruktur für Mitarbeiter {0} am angegebenen Datum {1} zugewiesen,
-No Staffing Plans found for this Designation,Für diese Bezeichnung wurden keine Stellenpläne gefunden,
+No Staffing Plans found for this Designation,Für diese Position wurden keine Stellenpläne gefunden,
 No Student Groups created.,Keine Studentengruppen erstellt.,
 No Students in,Keine Studenten in,
 No Tax Withholding data found for the current Fiscal Year.,Keine Steuerverweigerungsdaten für das aktuelle Geschäftsjahr gefunden.,
@@ -1735,7 +1735,6 @@
 Non Profit,Gemeinnützig,
 Non Profit (beta),Non-Profit (Beta),
 Non-GST outward supplies,Nicht-GST-Lieferungen nach außen,
-Non-Group to Group,Non-Group-Gruppe,
 None,Keiner,
 None of the items have any change in quantity or value.,Keiner der Artikel hat irgendeine Änderung bei Mengen oder Kosten.,
 Nos,Stk,
@@ -2027,7 +2026,7 @@
 Please select Category first,Bitte zuerst Kategorie auswählen,
 Please select Charge Type first,Bitte zuerst Chargentyp auswählen,
 Please select Company,Bitte Unternehmen auswählen,
-Please select Company and Designation,Bitte wählen Sie Unternehmen und Stelle,
+Please select Company and Designation,Bitte wählen Sie Unternehmen und Position,
 Please select Company and Posting Date to getting entries,"Bitte wählen Sie Unternehmen und Buchungsdatum, um Einträge zu erhalten",
 Please select Company first,Bitte zuerst Unternehmen auswählen,
 Please select Completion Date for Completed Asset Maintenance Log,Bitte wählen Sie Fertigstellungsdatum für das abgeschlossene Wartungsprotokoll für den Vermögenswert,
@@ -2772,7 +2771,7 @@
 Split Batch,Split Batch,
 Split Issue,Split-Problem,
 Sports,Sport,
-Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Bezeichnung {1},
+Staffing Plan {0} already exist for designation {1},Personalplan {0} existiert bereits für Position {1},
 Standard,Standard,
 Standard Buying,Standard-Kauf,
 Standard Selling,Standard-Vertrieb,
@@ -3710,7 +3709,7 @@
 Delivery Notes,Lieferscheine,
 Depreciated Amount,Abschreibungsbetrag,
 Description,Beschreibung,
-Designation,Bezeichnung,
+Designation,Position,
 Difference Value,Differenzwert,
 Dimension Filter,Dimensionsfilter,
 Disabled,Deaktiviert,
@@ -3920,7 +3919,7 @@
 Please enter GSTIN and state for the Company Address {0},Bitte geben Sie GSTIN ein und geben Sie die Firmenadresse {0} an.,
 Please enter Item Code to get item taxes,"Bitte geben Sie den Artikelcode ein, um die Artikelsteuern zu erhalten",
 Please enter Warehouse and Date,Bitte geben Sie Lager und Datum ein,
-Please enter the designation,Bitte geben Sie die Bezeichnung ein,
+Please enter the designation,Bitte geben Sie die Position ein,
 Please login as a Marketplace User to edit this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu bearbeiten.",
 Please login as a Marketplace User to report this item.,"Bitte melden Sie sich als Marketplace-Benutzer an, um diesen Artikel zu melden.",
 Please select <b>Template Type</b> to download template,"Bitte wählen Sie <b>Vorlagentyp</b> , um die Vorlage herunterzuladen",
@@ -6243,7 +6242,7 @@
 Create Sample Collection document for Lab Test,Erstellen Sie ein Probensammeldokument für den Labortest,
 Checking this will create a Sample Collection document  every time you create a Lab Test,"Wenn Sie dies aktivieren, wird jedes Mal, wenn Sie einen Labortest erstellen, ein Probensammeldokument erstellt",
 Employee name and designation in print,Name und Bezeichnung des Mitarbeiters im Druck,
-Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Bezeichnung des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.",
+Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.,"Aktivieren Sie diese Option, wenn Sie möchten, dass der Name und die Position des Mitarbeiters, der dem Benutzer zugeordnet ist, der das Dokument einreicht, im Labortestbericht gedruckt werden.",
 Do not print or email Lab Tests without Approval,Drucken oder senden Sie Labortests nicht ohne Genehmigung per E-Mail,
 Checking this will restrict printing and emailing of Lab Test documents unless they have the status as Approved.,"Wenn Sie dies aktivieren, wird das Drucken und E-Mailen von Labortestdokumenten eingeschränkt, sofern diese nicht den Status &quot;Genehmigt&quot; haben.",
 Custom Signature in Print,Kundenspezifische Unterschrift im Druck,
@@ -6499,7 +6498,7 @@
 Approver,Genehmiger,
 Required Skills,Benötigte Fähigkeiten,
 Skills,Kompetenzen,
-Designation Skill,Bezeichnung Fähigkeit,
+Designation Skill,Positions Fähigkeit,
 Skill,Fertigkeit,
 Driver,Fahrer/-in,
 HR-DRI-.YYYY.-,HR-DRI-.YYYY.-,
@@ -6798,7 +6797,7 @@
 Employment Type (optional),Anstellungsart (optional),
 Branch (optional),Zweigstelle (optional),
 Department (optional),Abteilung (optional),
-Designation (optional),Bezeichnung (optional),
+Designation (optional),Position (optional),
 Employee Grade (optional),Dienstgrad (optional),
 Employee (optional),Mitarbeiter (optional),
 Allocate Leaves,Blätter zuweisen,
@@ -7653,7 +7652,7 @@
 Buyer of Goods and Services.,Käufer von Waren und Dienstleistungen.,
 CUST-.YYYY.-,CUST-.YYYY.-,
 Default Company Bank Account,Standard-Bankkonto des Unternehmens,
-From Lead,Von Lead,
+From Lead,Aus Lead,
 Account Manager,Buchhalter,
 Allow Sales Invoice Creation Without Sales Order,Ermöglichen Sie die Erstellung von Kundenrechnungen ohne Auftrag,
 Allow Sales Invoice Creation Without Delivery Note,Ermöglichen Sie die Erstellung einer Ausgangsrechnung ohne Lieferschein,
@@ -7769,7 +7768,7 @@
 Applicable To (Role),Anwenden auf (Rolle),
 Applicable To (Employee),Anwenden auf (Mitarbeiter),
 Applicable To (User),Anwenden auf (Benutzer),
-Applicable To (Designation),Anwenden auf (Bezeichnung),
+Applicable To (Designation),Anwenden auf (Position),
 Approving Role (above authorized value),Genehmigende Rolle (über dem autorisierten Wert),
 Approving User  (above authorized value),Genehmigender Benutzer (über dem autorisierten Wert),
 Brand Defaults,Markenstandards,
@@ -8946,7 +8945,7 @@
 Requesting Department,Abteilung anfordern,
 Employee (Lab Technician),Mitarbeiter (Labortechniker),
 Lab Technician Name,Name des Labortechnikers,
-Lab Technician Designation,Bezeichnung des Labortechnikers,
+Lab Technician Designation,Position des Labortechnikers,
 Compound Test Result,Zusammengesetztes Testergebnis,
 Organism Test Result,Organismustestergebnis,
 Sensitivity Test Result,Empfindlichkeitstestergebnis,
@@ -9852,3 +9851,24 @@
 {}  Available,{} Verfügbar,
 Report an Issue,Ein Problem melden,
 User Forum,Anwenderforum,
+Get Customer Group Details,Einstellungen aus Kundengruppe übernehmen,
+Is Rate Adjustment Entry (Debit Note),Ist Preisanpassung (Belastungsanzeige),
+Fetch Timesheet,Zeiterfassung laden,
+Company Tax ID,Eigene Steuernummer,
+Quotation Number,Angebotsnummer,
+Company Shipping Address,Eigene Lieferadresse,
+Company Billing Address,Eigene Rechnungsadresse,
+Billing Address Details,Vorschau Rechnungsadresse,
+Supplier Contact,Lieferantenkontakt,
+Order Status,Bestellstatus,
+Invoice Portion (%),Rechnungsanteil (%),
+Discount Settings,Rabatt-Einstellungen,
+Payment Amount (Company Currency),Zahlungsbetrag (Unternehmenswährung),
+Putaway Rule,Einlagerungsregel,
+Apply Putaway Rule,Einlagerungsregel anwenden,
+Default Discount Account,Standard-Rabattkonto,
+Default Provisional Account,Standard Provisorisches Konto,
+Leave Type Allocation,Zuordnung Abwesenheitsarten,
+From Lead,Aus Lead,
+From Opportunity,Aus Chance,
+Publish in Website,Auf Webseite veröffentlichen,
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 8518156..22e3c35 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -175,7 +175,7 @@
 All Accounts,Tous les comptes,
 All Addresses.,Toutes les adresses.,
 All Assessment Groups,Tous les Groupes d'Évaluation,
-All BOMs,Toutes les LDM,
+All BOMs,Toutes les nomenclatures,
 All Contacts.,Tous les contacts.,
 All Customer Groups,Tous les Groupes Client,
 All Day,Toute la Journée,
@@ -330,16 +330,16 @@
 Avg. Buying Price List Rate,Moyenne de la liste de prix d'achat,
 Avg. Selling Price List Rate,Prix moyen de la liste de prix de vente,
 Avg. Selling Rate,Moy. Taux de vente,
-BOM,LDM (Liste de Matériaux),
-BOM Browser,Explorateur LDM,
-BOM No,N° LDM,
-BOM Rate,Taux LDM,
-BOM Stock Report,Rapport de Stock de LDM,
-BOM and Manufacturing Quantity are required,LDM et quantité de production sont nécessaires,
-BOM does not contain any stock item,LDM ne contient aucun article en stock,
-BOM {0} does not belong to Item {1},LDM {0} n’appartient pas à l'article {1},
-BOM {0} must be active,LDM {0} doit être active,
-BOM {0} must be submitted,LDM {0} doit être soumise,
+BOM,Nomenclature,
+BOM Browser,Explorateur Nomenclature,
+BOM No,N° Nomenclature,
+BOM Rate,Valeur nomenclature,
+BOM Stock Report,Rapport de Stock des nomenclatures,
+BOM and Manufacturing Quantity are required,Nomenclature et quantité de production sont nécessaires,
+BOM does not contain any stock item,Nomenclature ne contient aucun article en stock,
+BOM {0} does not belong to Item {1},Nomenclature {0} n’appartient pas à l'article {1},
+BOM {0} must be active,Nomenclature {0} doit être active,
+BOM {0} must be submitted,Nomenclature {0} doit être soumise,
 Balance,Solde,
 Balance (Dr - Cr),Balance (Dr - Cr),
 Balance ({0}),Solde ({0}),
@@ -386,8 +386,8 @@
 Bill,Facture,
 Bill Date,Date de la Facture,
 Bill No,Numéro de facture,
-Bill of Materials,Liste de Matériaux,
-Bill of Materials (BOM),Liste de Matériaux (LDM),
+Bill of Materials,Nomenclatures,
+Bill of Materials (BOM),Nomenclature,
 Billable Hours,Heures facturables,
 Billed,Facturé,
 Billed Amount,Montant facturé,
@@ -404,14 +404,14 @@
 Black,Noir,
 Blanket Orders from Costumers.,Commandes provisoires de clients.,
 Block Invoice,Bloquer la facture,
-Boms,Listes de Matériaux,
+Boms,Nomenclatures,
 Bonus Payment Date cannot be a past date,La date de paiement du bonus ne peut pas être une date passée,
 Both Trial Period Start Date and Trial Period End Date must be set,La date de début de la période d&#39;essai et la date de fin de la période d&#39;essai doivent être définies,
 Both Warehouse must belong to same Company,Les deux Entrepôt doivent appartenir à la même Société,
 Branch,Branche,
 Broadcasting,Radio/Télévision,
 Brokerage,Courtage,
-Browse BOM,Parcourir la LDM,
+Browse BOM,Parcourir la nomenclature,
 Budget Against,Budget Pour,
 Budget List,Liste budgétaire,
 Budget Variance Report,Rapport d’Écarts de Budget,
@@ -467,7 +467,7 @@
 Cannot covert to Group because Account Type is selected.,Conversion impossible en Groupe car le Type de Compte est sélectionné.,
 Cannot create Retention Bonus for left Employees,Impossible de créer une prime de fidélisation pour les employés ayant quitté l'entreprise,
 Cannot create a Delivery Trip from Draft documents.,Impossible de créer un voyage de livraison à partir de documents brouillons.,
-Cannot deactivate or cancel BOM as it is linked with other BOMs,Désactivation ou annulation de la LDM impossible car elle est liée avec d'autres LDMs,
+Cannot deactivate or cancel BOM as it is linked with other BOMs,Désactivation ou annulation de la nomenclature impossible car elle est liée avec d'autres nomenclatures,
 "Cannot declare as lost, because Quotation has been made.","Impossible de déclarer comme perdu, parce que le Devis a été fait.",
 Cannot deduct when category is for 'Valuation' or 'Valuation and Total',Déduction impossible lorsque la catégorie est pour 'Évaluation' ou 'Vaulation et Total',
 Cannot deduct when category is for 'Valuation' or 'Vaulation and Total',Vous ne pouvez pas déduire lorsqu'une catégorie est pour 'Évaluation' ou 'Évaluation et Total',
@@ -722,7 +722,7 @@
 Currency should be same as Price List Currency: {0},La devise doit être la même que la devise de la liste de prix: {0},
 Current,Actuel,
 Current Assets,Actifs Actuels,
-Current BOM and New BOM can not be same,La LDM actuelle et la nouvelle LDM ne peuvent être pareilles,
+Current BOM and New BOM can not be same,La nomenclature actuelle et la nouvelle nomenclature ne peuvent être pareilles,
 Current Job Openings,Offres d'Emploi Actuelles,
 Current Liabilities,Dettes Actuelles,
 Current Qty,Qté actuelle,
@@ -780,9 +780,9 @@
 Declare Lost,Déclarer perdu,
 Deduction,Déduction,
 Default Activity Cost exists for Activity Type - {0},Un Coût d’Activité par défault existe pour le Type d’Activité {0},
-Default BOM ({0}) must be active for this item or its template,LDM par défaut ({0}) doit être actif pour ce produit ou son modèle,
-Default BOM for {0} not found,LDM par défaut {0} introuvable,
-Default BOM not found for Item {0} and Project {1},La LDM par défaut n'a pas été trouvée pour l'Article {0} et le Projet {1},
+Default BOM ({0}) must be active for this item or its template,Nomenclature par défaut ({0}) doit être actif pour ce produit ou son modèle,
+Default BOM for {0} not found,Nomenclature par défaut {0} introuvable,
+Default BOM not found for Item {0} and Project {1},La nomenclature par défaut n'a pas été trouvée pour l'Article {0} et le Projet {1},
 Default Letter Head,En-Tête de Courrier par Défaut,
 Default Tax Template,Modèle de Taxes par Défaut,
 Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,L’Unité de Mesure par Défaut pour l’Article {0} ne peut pas être modifiée directement parce que vous avez déjà fait une (des) transaction (s) avec une autre unité de mesure. Vous devez créer un nouvel article pour utiliser une UDM par défaut différente.,
@@ -1023,7 +1023,7 @@
 Female,Féminin,
 Fetch Data,Récupérer des données,
 Fetch Subscription Updates,Vérifier les mises à jour des abonnements,
-Fetch exploded BOM (including sub-assemblies),Récupérer la LDM éclatée (y compris les sous-ensembles),
+Fetch exploded BOM (including sub-assemblies),Récupérer la nomenclature éclatée (y compris les sous-ensembles),
 Fetching records......,Récupération des enregistrements ......,
 Field Name,Nom du Champ,
 Fieldname,Nom du Champ,
@@ -1135,7 +1135,7 @@
 Get Invocies,Obtenir des invocies,
 Get Invoices,Obtenir des factures,
 Get Invoices based on Filters,Obtenir les factures en fonction des filtres,
-Get Items from BOM,Obtenir les Articles depuis LDM,
+Get Items from BOM,Obtenir les Articles depuis nomenclature,
 Get Items from Healthcare Services,Obtenir des articles des services de santé,
 Get Items from Prescriptions,Obtenir des articles des prescriptions,
 Get Items from Product Bundle,Obtenir les Articles du Produit Groupé,
@@ -1425,8 +1425,8 @@
 Last Purchase Price,Dernier prix d'achat,
 Last Purchase Rate,Dernier Prix d'Achat,
 Latest,Dernier,
-Latest price updated in all BOMs,Prix les plus récents mis à jour dans toutes les LDMs,
-Lead,Conduire,
+Latest price updated in all BOMs,Prix les plus récents mis à jour dans toutes les nomenclatures,
+Lead,Prospect,
 Lead Count,Nombre de Prospects,
 Lead Owner,Responsable du Prospect,
 Lead Owner cannot be same as the Lead,Le Responsable du Prospect ne peut pas être identique au Prospect,
@@ -1655,7 +1655,7 @@
 Net pay cannot be negative,Salaire Net ne peut pas être négatif,
 New Account Name,Nouveau Nom de Compte,
 New Address,Nouvelle adresse,
-New BOM,Nouvelle LDM,
+New BOM,Nouvelle nomenclature,
 New Batch ID (Optional),Nouveau Numéro de Lot (Optionnel),
 New Batch Qty,Nouvelle Qté de Lot,
 New Company,Nouvelle Société,
@@ -1689,7 +1689,7 @@
 No Items available for transfer,Aucun article disponible pour le transfert,
 No Items selected for transfer,Aucun article sélectionné pour le transfert,
 No Items to pack,Pas d’Articles à emballer,
-No Items with Bill of Materials to Manufacture,Aucun Article avec une Liste de Matériel à Produire,
+No Items with Bill of Materials to Manufacture,Aucun Article avec une nomenclature à Produire,
 No Items with Bill of Materials.,Aucun article avec nomenclature.,
 No Permission,Aucune autorisation,
 No Remarks,Aucune Remarque,
@@ -1777,7 +1777,7 @@
 Only Leave Applications with status 'Approved' and 'Rejected' can be submitted,Seules les Demandes de Congés avec le statut 'Appouvée' ou 'Rejetée' peuvent être soumises,
 "Only the Student Applicant with the status ""Approved"" will be selected in the table below.",Seul les candidatures étudiantes avec le statut «Approuvé» seront sélectionnées dans le tableau ci-dessous.,
 Only users with {0} role can register on Marketplace,Seuls les utilisateurs ayant le rôle {0} peuvent s'inscrire sur Marketplace,
-Open BOM {0},Ouvrir LDM {0},
+Open BOM {0},Ouvrir nomenclature {0},
 Open Item {0},Ouvrir l'Article {0},
 Open Notifications,Notifications ouvertes,
 Open Orders,Commandes ouvertes,
@@ -2015,9 +2015,9 @@
 Please save the report again to rebuild or update,Veuillez enregistrer le rapport à nouveau pour reconstruire ou mettre à jour,
 "Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row","Veuillez sélectionner le Montant Alloué, le Type de Facture et le Numéro de Facture dans au moins une ligne",
 Please select Apply Discount On,Veuillez sélectionnez Appliquer Remise Sur,
-Please select BOM against item {0},Veuillez sélectionner la liste de matériaux (LDM) pour l'article {0},
-Please select BOM for Item in Row {0},Veuillez sélectionnez une LDM pour l’Article à la Ligne {0},
-Please select BOM in BOM field for Item {0},Veuillez sélectionner une LDM dans le champ LDM pour l’Article {0},
+Please select BOM against item {0},Veuillez sélectionner la nomenclature pour l'article {0},
+Please select BOM for Item in Row {0},Veuillez sélectionnez une nomenclature pour l’Article à la Ligne {0},
+Please select BOM in BOM field for Item {0},Veuillez sélectionner une nomenclature dans le champ nomenclature pour l’Article {0},
 Please select Category first,Veuillez d’abord sélectionner une Catégorie,
 Please select Charge Type first,Veuillez d’abord sélectionner le Type de Facturation,
 Please select Company,Veuillez sélectionner une Société,
@@ -2044,7 +2044,7 @@
 Please select Sample Retention Warehouse in Stock Settings first,Veuillez d'abord définir un entrepôt de stockage des échantillons dans les paramètres de stock,
 Please select Start Date and End Date for Item {0},Veuillez sélectionner la Date de Début et Date de Fin pour l'Article {0},
 Please select Student Admission which is mandatory for the paid student applicant,Veuillez sélectionner obligatoirement une Admission d'Étudiant pour la candidature étudiante payée,
-Please select a BOM,Veuillez sélectionner une LDM,
+Please select a BOM,Veuillez sélectionner une nomenclature,
 Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement,Veuillez sélectionner un Lot pour l'Article {0}. Impossible de trouver un seul lot satisfaisant à cette exigence,
 Please select a Company,Veuillez sélectionner une Société,
 Please select a batch,Veuillez sélectionner un lot,
@@ -2273,8 +2273,8 @@
 Quantity to Produce,Quantité à produire,
 Quantity to Produce can not be less than Zero,La quantité à produire ne peut être inférieure à zéro,
 Query Options,Options de Requête,
-Queued for replacing the BOM. It may take a few minutes.,En file d'attente pour remplacer la LDM. Cela peut prendre quelques minutes.,
-Queued for updating latest price in all Bill of Materials. It may take a few minutes.,Mise à jour des prix les plus récents dans toutes les Listes de Matériaux en file d'attente. Cela peut prendre quelques minutes.,
+Queued for replacing the BOM. It may take a few minutes.,En file d'attente pour remplacer la nomenclature. Cela peut prendre quelques minutes.,
+Queued for updating latest price in all Bill of Materials. It may take a few minutes.,Mise à jour des prix les plus récents dans toutes les nomenclatures en file d'attente. Cela peut prendre quelques minutes.,
 Quick Journal Entry,Écriture Rapide dans le Journal,
 Quot Count,Compte de Devis,
 Quot/Lead %,Devis / Prospects %,
@@ -2354,7 +2354,7 @@
 Reorder Qty,Qté de Réapprovisionnement,
 Repeat Customer Revenue,Revenus de Clients Récurrents,
 Repeat Customers,Clients Récurrents,
-Replace BOM and update latest price in all BOMs,Remplacer la LDM et actualiser les prix les plus récents dans toutes les LDMs,
+Replace BOM and update latest price in all BOMs,Remplacer la nomenclature et actualiser les prix les plus récents dans toutes les nomenclatures,
 Replied,Répondu,
 Replies,réponses,
 Report,Rapport,
@@ -2466,11 +2466,11 @@
 Row {0}: Allocated amount {1} must be less than or equals to Payment Entry amount {2},Ligne {0} : Le montant alloué {1} doit être inférieur ou égal au montant du Paiement {2},
 Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2},Ligne {0} : Le montant alloué {1} doit être inférieur ou égal au montant restant sur la Facture {2},
 Row {0}: An Reorder entry already exists for this warehouse {1},Ligne {0} : Une écriture de Réapprovisionnement existe déjà pour cet entrepôt {1},
-Row {0}: Bill of Materials not found for the Item {1},Ligne {0} : Liste de Matériaux non trouvée pour l’Article {1},
+Row {0}: Bill of Materials not found for the Item {1},Ligne {0} : Nomenclature non trouvée pour l’Article {1},
 Row {0}: Conversion Factor is mandatory,Ligne {0} : Le Facteur de Conversion est obligatoire,
 Row {0}: Cost center is required for an item {1},Ligne {0}: le Centre de Coûts est requis pour un article {1},
 Row {0}: Credit entry can not be linked with a {1},Ligne {0} : L’Écriture de crédit ne peut pas être liée à un {1},
-Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2},Ligne {0} : La devise de la LDM #{1} doit être égale à la devise sélectionnée {2},
+Row {0}: Currency of the BOM #{1} should be equal to the selected currency {2},Ligne {0} : La devise de la nomenclature #{1} doit être égale à la devise sélectionnée {2},
 Row {0}: Debit entry can not be linked with a {1},Ligne {0} : L’Écriture de Débit ne peut pas être lié à un {1},
 Row {0}: Depreciation Start Date is required,Ligne {0}: la date de début de l'amortissement est obligatoire,
 Row {0}: Enter location for the asset item {1},Ligne {0}: entrez la localisation de l'actif {1},
@@ -2490,7 +2490,7 @@
 Row {0}: Please set the correct code on Mode of Payment {1},Ligne {0}: définissez le code correct sur le mode de paiement {1}.,
 Row {0}: Qty is mandatory,Ligne {0} : Qté obligatoire,
 Row {0}: Quality Inspection rejected for item {1},Ligne {0}: le contrôle qualité a été rejeté pour l'élément {1}.,
-Row {0}: UOM Conversion Factor is mandatory,Ligne {0} : Facteur de Conversion LDM est obligatoire,
+Row {0}: UOM Conversion Factor is mandatory,Ligne {0} : Facteur de Conversion nomenclature est obligatoire,
 Row {0}: select the workstation against the operation {1},Ligne {0}: sélectionnez le poste de travail en fonction de l'opération {1},
 Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.,Ligne {0}: {1} Numéros de série requis pour l'article {2}. Vous en avez fourni {3}.,
 Row {0}: {1} must be greater than 0,Ligne {0}: {1} doit être supérieure à 0,
@@ -2587,8 +2587,8 @@
 Select,Sélectionner,
 Select Alternate Item,Sélectionnez un autre élément,
 Select Attribute Values,Sélectionner les valeurs d'attribut,
-Select BOM,Sélectionner LDM,
-Select BOM and Qty for Production,Sélectionner la LDM et la Qté pour la Production,
+Select BOM,Sélectionner une nomenclature,
+Select BOM and Qty for Production,Sélectionner la nomenclature et la Qté pour la Production,
 "Select BOM, Qty and For Warehouse","Sélectionner une nomenclature, une quantité et un entrepôt",
 Select Batch,Sélectionnez le Lot,
 Select Batch Numbers,Sélectionnez les Numéros de Lot,
@@ -2760,7 +2760,7 @@
 Source and target warehouse must be different,Entrepôt source et destination doivent être différents,
 Source of Funds (Liabilities),Source des Fonds (Passif),
 Source warehouse is mandatory for row {0},Entrepôt source est obligatoire à la ligne {0},
-Specified BOM {0} does not exist for Item {1},La LDM {0} spécifiée n'existe pas pour l'Article {1},
+Specified BOM {0} does not exist for Item {1},La nomenclature {0} spécifiée n'existe pas pour l'Article {1},
 Split,Fractionner,
 Split Batch,Lot Fractionné,
 Split Issue,Diviser le ticket,
@@ -2888,11 +2888,11 @@
 Supplies made to Unregistered Persons,Fournitures faites à des personnes non inscrites,
 Suppliies made to Composition Taxable Persons,Suppleies à des personnes assujetties à la composition,
 Supply Type,Type d'approvisionnement,
-Support,Soutien,
-Support Analytics,Analyse du Support,
-Support Settings,Paramètres du Support,
-Support Tickets,Billets de Support,
-Support queries from customers.,Demande de support des clients,
+Support,"Assistance/Support",
+Support Analytics,Analyse de l'assistance,
+Support Settings,Paramètres du module Assistance,
+Support Tickets,Ticket d'assistance,
+Support queries from customers.,Demande d'assistance des clients,
 Susceptible,Sensible,
 Sync has been temporarily disabled because maximum retries have been exceeded,La synchronisation a été temporairement désactivée car les tentatives maximales ont été dépassées,
 Syntax error in condition: {0},Erreur de syntaxe dans la condition: {0},
@@ -2965,7 +2965,7 @@
 The name of your company for which you are setting up this system.,Le nom de l'entreprise pour laquelle vous configurez ce système.,
 The number of shares and the share numbers are inconsistent,Le nombre d'actions dans les transactions est incohérent avec le nombre total d'actions,
 The payment gateway account in plan {0} is different from the payment gateway account in this payment request,Le compte passerelle de paiement dans le plan {0} est différent du compte passerelle de paiement dans cette requête de paiement.,
-The selected BOMs are not for the same item,Les LDMs sélectionnées ne sont pas pour le même article,
+The selected BOMs are not for the same item,Les nomenclatures sélectionnées ne sont pas pour le même article,
 The selected item cannot have Batch,L’article sélectionné ne peut pas avoir de Lot,
 The seller and the buyer cannot be the same,Le vendeur et l'acheteur ne peuvent pas être les mêmes,
 The shareholder does not belong to this company,L'actionnaire n'appartient pas à cette société,
@@ -3150,7 +3150,7 @@
 Travel,Déplacement,
 Travel Expenses,Frais de Déplacement,
 Tree Type,Type d'Arbre,
-Tree of Bill of Materials,Arbre des Listes de Matériaux,
+Tree of Bill of Materials,Arbre des Nomenclatures,
 Tree of Item Groups.,Arbre de Groupes d’Articles .,
 Tree of Procedures,Arbre de procédures,
 Tree of Quality Procedures.,Arbre de la qualité des procédures.,
@@ -3305,7 +3305,7 @@
 WooCommerce Products,Produits WooCommerce,
 Work In Progress,Travaux en cours,
 Work Order,Ordre de travail,
-Work Order already created for all items with BOM,Ordre de travail déjà créé pour tous les articles avec une LDM,
+Work Order already created for all items with BOM,Ordre de travail déjà créé pour tous les articles avec une nomenclature,
 Work Order cannot be raised against a Item Template,Un ordre de travail ne peut pas être créé pour un modèle d'article,
 Work Order has been {0},L'ordre de travail a été {0},
 Work Order not created,Ordre de travail non créé,
@@ -3326,7 +3326,7 @@
 You are not authorized to approve leaves on Block Dates,Vous n'êtes pas autorisé à approuver les congés sur les Dates Bloquées,
 You are not authorized to set Frozen value,Vous n'êtes pas autorisé à définir des valeurs gelées,
 You are not present all day(s) between compensatory leave request days,Vous n'êtes pas présent(e) tous les jours vos demandes de congé compensatoire,
-You can not change rate if BOM mentioned agianst any item,Vous ne pouvez pas modifier le taux si la LDM est mentionnée pour un article,
+You can not change rate if BOM mentioned agianst any item,Vous ne pouvez pas modifier le taux si la nomenclature est mentionnée pour un article,
 You can not enter current voucher in 'Against Journal Entry' column,Vous ne pouvez pas entrer le bon actuel dans la colonne 'Pour l'Écriture de Journal',
 You can only have Plans with the same billing cycle in a Subscription,Vous ne pouvez avoir que des plans ayant le même cycle de facturation dans le même abonnement,
 You can only redeem max {0} points in this order.,Vous pouvez uniquement échanger un maximum de {0} points dans cet commande.,
@@ -5502,7 +5502,7 @@
 Blanket Order Rate,Prix unitaire de commande avec limites,
 Returned Qty,Qté Retournée,
 Purchase Order Item Supplied,Article Fourni du Bon de Commande,
-BOM Detail No,N° de Détail LDM,
+BOM Detail No,N° de Détail de la nomenclature,
 Stock Uom,UDM du Stock,
 Raw Material Item Code,Code d’Article de Matière Première,
 Supplied Qty,Qté Fournie,
@@ -5600,7 +5600,6 @@
 Received By,Reçu par,
 Caller Information,Informations sur l&#39;appelant,
 Contact Name,Nom du Contact,
-Lead ,Conduire,
 Lead Name,Nom du Prospect,
 Ringing,Sonnerie,
 Missed,Manqué,
@@ -7183,7 +7182,7 @@
 Ordered Quantity,Quantité Commandée,
 Item to be manufactured or repacked,Article à produire ou à réemballer,
 Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Quantité d'article obtenue après production / reconditionnement des quantités données de matières premières,
-Set rate of sub-assembly item based on BOM,Définir le prix des articles de sous-assemblage en fonction de la LDM,
+Set rate of sub-assembly item based on BOM,Définir le prix des articles de sous-assemblage en fonction de la nomenclature,
 Allow Alternative Item,Autoriser un article alternatif,
 Item UOM,UDM de l'Article,
 Conversion Rate,Taux de Conversion,
@@ -7214,33 +7213,33 @@
 Show Items,Afficher les Articles,
 Show Operations,Afficher Opérations,
 Website Description,Description du Site Web,
-BOM Explosion Item,Article Eclaté LDM,
+BOM Explosion Item,Article Eclaté en nomenclature,
 Qty Consumed Per Unit,Qté Consommée Par Unité,
 Include Item In Manufacturing,Inclure l&#39;article dans la fabrication,
-BOM Item,Article LDM,
+BOM Item,Article de la nomenclature,
 Item operation,Opération de l'article,
 Rate & Amount,Taux et Montant,
 Basic Rate (Company Currency),Taux de Base (Devise de la Société ),
 Scrap %,% de Rebut,
 Original Item,Article original,
-BOM Operation,Opération LDM,
+BOM Operation,Opération de la nomenclature (gamme),
 Operation Time ,Durée de l&#39;opération,
 In minutes,En minutes,
 Batch Size,Taille du lot,
 Base Hour Rate(Company Currency),Taux Horaire de Base (Devise de la Société),
 Operating Cost(Company Currency),Coût d'Exploitation (Devise Société),
-BOM Scrap Item,Article Mis au Rebut LDM,
+BOM Scrap Item,Article Mis au Rebut dans la nomenclature,
 Basic Amount (Company Currency),Montant de Base (Devise de la Société),
-BOM Update Tool,Outil de mise à jour de LDM,
-"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate ""BOM Explosion Item"" table as per new BOM.\nIt also updates latest price in all the BOMs.","Remplacez une LDM particulière dans toutes les LDM où elles est utilisée. Cela remplacera le lien vers l'ancienne LDM, mettra à jour les coûts et régénérera le tableau ""Article Explosé de LDM"" selon la nouvelle LDM. Cela mettra également à jour les prix les plus récents dans toutes les LDMs.",
-Replace BOM,Remplacer la LDM,
-Current BOM,LDM Actuelle,
-The BOM which will be replaced,La LDM qui sera remplacée,
-The new BOM after replacement,La nouvelle LDM après remplacement,
+BOM Update Tool,Outil de mise à jour des Nomenclatures,
+"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate ""BOM Explosion Item"" table as per new BOM.\nIt also updates latest price in all the BOMs.","Remplacez une nomenclature particulière dans toutes les nomenclatures où elles est utilisée. Cela remplacera le lien vers l'ancienne nomenclature, mettra à jour les coûts et régénérera le tableau ""Article Explosé de nomenclature"" selon la nouvelle nomenclature. Cela mettra également à jour les prix les plus récents dans toutes les nomenclatures.",
+Replace BOM,Remplacer la nomenclature,
+Current BOM,nomenclature Actuelle,
+The BOM which will be replaced,La nomenclature qui sera remplacée,
+The new BOM after replacement,La nouvelle nomenclature après remplacement,
 Replace,Remplacer,
-Update latest price in all BOMs,Mettre à jour le prix le plus récent dans toutes les LDMs,
-BOM Website Item,Article de LDM du Site Internet,
-BOM Website Operation,Opération de LDM du Site Internet,
+Update latest price in all BOMs,Mettre à jour le prix le plus récent dans toutes les nomenclatures,
+BOM Website Item,Article de nomenclature du Site Internet,
+BOM Website Operation,Opération de nomenclature du Site Internet,
 Operation Time,Heure de l'Opération,
 PO-JOB.#####,PO-JOB. #####,
 Timing Detail,Détail du timing,
@@ -7272,7 +7271,7 @@
 Overproduction Percentage For Sales Order,Pourcentage de surproduction pour les commandes client,
 Overproduction Percentage For Work Order,Pourcentage de surproduction pour les ordres de travail,
 Other Settings,Autres Paramètres,
-Update BOM Cost Automatically,Mettre à jour automatiquement le coût de la LDM,
+Update BOM Cost Automatically,Mettre à jour automatiquement le coût de la nomenclature,
 Material Request Plan Item,Article du plan de demande de matériel,
 Material Request Type,Type de Demande de Matériel,
 Material Issue,Sortie de Matériel,
@@ -7312,7 +7311,7 @@
 Item To Manufacture,Article à produire,
 Material Transferred for Manufacturing,Matériel Transféré pour la Production,
 Manufactured Qty,Qté Produite,
-Use Multi-Level BOM,Utiliser LDM à Plusieurs Niveaux,
+Use Multi-Level BOM,Utiliser les nomenclatures à plusieurs niveaux,
 Plan material for sub-assemblies,Plan de matériaux pour les sous-ensembles,
 Skip Material Transfer to WIP Warehouse,Ignorer le transfert de matériel vers l'entrepôt WIP,
 Check if material transfer entry is not required,Vérifiez si une un transfert de matériel n'est pas requis,
@@ -7685,7 +7684,7 @@
 Expected Amount,Montant prévu,
 POS Closing Voucher Invoices,Factures du bon de clôture du PDV,
 Quantity of Items,Quantité d'articles,
-"Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have ""Is Stock Item"" as ""No"" and ""Is Sales Item"" as ""Yes"".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials","Regroupement d' **Articles** dans un autre **Article**. Ceci est utile si vous regroupez certains **Articles** dans un lot et que vous maintenez l'inventaire des **Articles** du lot et non de l'**Article** composé. L'**Article** composé aura ""Article En Stock"" à ""Non"" et ""Article À Vendre"" à ""Oui"". Exemple : Si vous vendez des Ordinateurs Portables et Sacs à Dos séparément et qu'il y a un prix spécial si le client achète les deux, alors l'Ordinateur Portable + le Sac à Dos sera un nouveau Produit Groupé. Remarque: LDM = Liste\nDes Matériaux",
+"Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have ""Is Stock Item"" as ""No"" and ""Is Sales Item"" as ""Yes"".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials","Regroupement d' **Articles** dans un autre **Article**. Ceci est utile si vous regroupez certains **Articles** dans un lot et que vous maintenez l'inventaire des **Articles** du lot et non de l'**Article** composé. L'**Article** composé aura ""Article En Stock"" à ""Non"" et ""Article À Vendre"" à ""Oui"". Exemple : Si vous vendez des Ordinateurs Portables et Sacs à Dos séparément et qu'il y a un prix spécial si le client achète les deux, alors l'Ordinateur Portable + le Sac à Dos sera un nouveau Produit Groupé.",
 Parent Item,Article Parent,
 List items that form the package.,Liste des articles qui composent le paquet.,
 SAL-QTN-.YYYY.-,SAL-QTN-. AAAA.-,
@@ -8089,7 +8088,7 @@
 Inspection Criteria,Critères d'Inspection,
 Inspection Required before Purchase,Inspection Requise avant Achat,
 Inspection Required before Delivery,Inspection Requise avant Livraison,
-Default BOM,LDM par Défaut,
+Default BOM,Nomenclature par Défaut,
 Supply Raw Materials for Purchase,Fournir les Matières Premières pour l'Achat,
 If subcontracted to a vendor,Si sous-traité à un fournisseur,
 Customer Code,Code Client,
@@ -8295,7 +8294,7 @@
 Sales Invoice No,N° de la Facture de Vente,
 Purchase Receipt No,N° du Reçu d'Achat,
 Inspection Required,Inspection obligatoire,
-From BOM,De LDM,
+From BOM,Depuis la nomenclature,
 For Quantity,Pour la Quantité,
 As per Stock UOM,Selon UDM du Stock,
 Including items for sub assemblies,Incluant les articles pour des sous-ensembles,
@@ -8316,7 +8315,7 @@
 Basic Amount,Montant de Base,
 Additional Cost,Frais Supplémentaire,
 Serial No / Batch,N° de Série / Lot,
-BOM No. for a Finished Good Item,N° d’Article Produit Fini LDM,
+BOM No. for a Finished Good Item,N° de nomenclature pour un d’Article (Produit Fini),
 Material Request used to make this Stock Entry,Demande de Matériel utilisée pour réaliser cette Écriture de Stock,
 Subcontracted Item,Article sous-traité,
 Against Stock Entry,Contre entrée de stock,
@@ -8456,9 +8455,9 @@
 Batch Item Expiry Status,Statut d'Expiration d'Article du Lot,
 Batch-Wise Balance History,Historique de Balance des Lots,
 BOM Explorer,Explorateur de nomenclature,
-BOM Search,Recherche LDM,
-BOM Stock Calculated,Stock calculé par liste de matériaux (LDM),
-BOM Variance Report,Rapport de variance par liste de matériaux (LDM),
+BOM Search,Recherche nomenclature,
+BOM Stock Calculated,Stock calculé par nomenclature,
+BOM Variance Report,Rapport de variance par nomenclature,
 Campaign Efficiency,Efficacité des Campagnes,
 Cash Flow,Flux de Trésorerie,
 Completed Work Orders,Ordres de travail terminés,
@@ -9873,3 +9872,7 @@
 Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries
 "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez réceptionner en plus de la quantité commandée. Par exemple, vous avez commandé 100 unités, votre pourcentage de dépassement est de 10%, vous pourrez réceptionner 110 unités"
 Unit Of Measure (UOM),Unité de mesure (UDM),
+Allowed Items,Articles autorisés
+Party Specific Item,Restriction d'article disponible
+Restrict Items Based On,Type de critére de restriction
+Based On Value,critére de restriction