Merge pull request #30080 from deepeshgarg007/item_wise_sales_history

fix: Item-wise sales history report
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 9e2cdff..ab1061b 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -120,6 +120,7 @@
 	prev_gl_entry = frappe.db.sql('''
 		select name, posting_date from `tabGL Entry` where company=%s and account=%s and
 		voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+		and is_cancelled = 0
 		order by posting_date desc limit 1
 	''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
 
@@ -227,6 +228,7 @@
 	gl_entries_details = frappe.db.sql('''
 		select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
 		from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+		and is_cancelled = 0
 		group by voucher_detail_no
 	'''.format(total_credit_debit, total_credit_debit_currency),
 		(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
@@ -282,7 +284,7 @@
 			return
 
 		# check if books nor frozen till endate:
-		if getdate(end_date) >= getdate(accounts_frozen_upto):
+		if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
 			end_date = get_last_day(add_days(accounts_frozen_upto, 1))
 
 		if via_journal_entry:
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index a9bc028..b2b818a 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -194,8 +194,14 @@
 			frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
 
 		frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
-		frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
-			(frm.doc.paid_from_account_currency != company_currency));
+
+		if (frm.doc.payment_type == "Pay") {
+			frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
+				(frm.doc.paid_to_account_currency != company_currency));
+		} else {
+			frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
+				(frm.doc.paid_from_account_currency != company_currency));
+		}
 
 		frm.toggle_display("base_received_amount", (
 			frm.doc.paid_to_account_currency != company_currency
@@ -230,7 +236,8 @@
 		var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
 
 		frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
-			"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
+			"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax",
+			"base_total_taxes_and_charges"], company_currency);
 
 		frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
 		frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@@ -339,6 +346,8 @@
 			}
 			frm.set_party_account_based_on_party = true;
 
+			let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
+
 			return frappe.call({
 				method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details",
 				args: {
@@ -372,7 +381,11 @@
 								if (r.message.bank_account) {
 									frm.set_value("bank_account", r.message.bank_account);
 								}
-							}
+							},
+							() => frm.events.set_current_exchange_rate(frm, "source_exchange_rate",
+									frm.doc.paid_from_account_currency, company_currency),
+							() => frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
+									frm.doc.paid_to_account_currency, company_currency)
 						]);
 					}
 				}
@@ -476,14 +489,14 @@
 	},
 
 	paid_from_account_currency: function(frm) {
-		if(!frm.doc.paid_from_account_currency) return;
-		var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
+		if(!frm.doc.paid_from_account_currency || !frm.doc.company) return;
+		let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
 
 		if (frm.doc.paid_from_account_currency == company_currency) {
 			frm.set_value("source_exchange_rate", 1);
 		} else if (frm.doc.paid_from){
 			if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
-				var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
+				let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
 				frappe.call({
 					method: "erpnext.setup.utils.get_exchange_rate",
 					args: {
@@ -503,8 +516,8 @@
 	},
 
 	paid_to_account_currency: function(frm) {
-		if(!frm.doc.paid_to_account_currency) return;
-		var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
+		if(!frm.doc.paid_to_account_currency || !frm.doc.company) return;
+		let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
 
 		frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
 			frm.doc.paid_to_account_currency, company_currency);
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index c8d1db9..3fc1adf 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -66,7 +66,9 @@
   "tax_withholding_category",
   "section_break_56",
   "taxes",
+  "section_break_60",
   "base_total_taxes_and_charges",
+  "column_break_61",
   "total_taxes_and_charges",
   "deductions_or_loss_section",
   "deductions",
@@ -715,12 +717,21 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Paid To Account Type"
+  },
+  {
+   "fieldname": "column_break_61",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_60",
+   "fieldtype": "Section Break",
+   "hide_border": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-24 18:58:24.919764",
+ "modified": "2022-02-23 20:08:39.559814",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry",
@@ -763,6 +774,7 @@
  "show_name_in_global_search": 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/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c36c843..f9f3350 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -934,8 +934,12 @@
 
 			tax.base_total = tax.total * self.source_exchange_rate
 
-			self.total_taxes_and_charges += current_tax_amount
-			self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
+			if self.payment_type == 'Pay':
+				self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
+				self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
+			else:
+				self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
+				self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
 
 		if self.get('taxes'):
 			self.paid_amount_after_tax = self.get('taxes')[-1].base_total
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index cc3528e..349b8bb 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -633,6 +633,45 @@
 		self.assertEqual(flt(expected_party_balance), party_balance)
 		self.assertEqual(flt(expected_party_account_balance), party_account_balance)
 
+	def test_multi_currency_payment_entry_with_taxes(self):
+		payment_entry = create_payment_entry(party='_Test Supplier USD', paid_to = '_Test Payable USD - _TC',
+			save=True)
+		payment_entry.append('taxes', {
+			'account_head': '_Test Account Service Tax - _TC',
+			'charge_type': 'Actual',
+			'tax_amount': 10,
+			'add_deduct_tax': 'Add',
+			'description': 'Test'
+		})
+
+		payment_entry.save()
+		self.assertEqual(payment_entry.base_total_taxes_and_charges, 10)
+		self.assertEqual(flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2))
+
+def create_payment_entry(**args):
+	payment_entry = frappe.new_doc('Payment Entry')
+	payment_entry.company = args.get('company') or '_Test Company'
+	payment_entry.payment_type = args.get('payment_type') or 'Pay'
+	payment_entry.party_type = args.get('party_type') or 'Supplier'
+	payment_entry.party = args.get('party') or '_Test Supplier'
+	payment_entry.paid_from = args.get('paid_from') or '_Test Bank - _TC'
+	payment_entry.paid_to = args.get('paid_to') or 'Creditors - _TC'
+	payment_entry.paid_amount = args.get('paid_amount') or 1000
+
+	payment_entry.setup_party_account_field()
+	payment_entry.set_missing_values()
+	payment_entry.set_exchange_rate()
+	payment_entry.received_amount = payment_entry.paid_amount / payment_entry.target_exchange_rate
+	payment_entry.reference_no = 'Test001'
+	payment_entry.reference_date = nowdate()
+
+	if args.get('save'):
+		payment_entry.save()
+		if args.get('submit'):
+			payment_entry.submit()
+
+	return payment_entry
+
 def create_payment_terms_template():
 
 	create_payment_term('Basic Amount Receivable')
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index ddca68a..d4513c6 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -84,20 +84,12 @@
 		sales_invoice.set_posting_time = 1
 		sales_invoice.posting_date = getdate(self.posting_date)
 		sales_invoice.save()
-		self.write_off_fractional_amount(sales_invoice, data)
 		sales_invoice.submit()
 
 		self.consolidated_invoice = sales_invoice.name
 
 		return sales_invoice.name
 
-	def write_off_fractional_amount(self, invoice, data):
-		pos_invoice_grand_total = sum(d.grand_total for d in data)
-
-		if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
-			invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
-			invoice.save()
-
 	def process_merging_into_credit_note(self, data):
 		credit_note = self.get_new_sales_invoice()
 		credit_note.is_return = 1
@@ -110,7 +102,6 @@
 		# TODO: return could be against multiple sales invoice which could also have been consolidated?
 		# credit_note.return_against = self.consolidated_invoice
 		credit_note.save()
-		self.write_off_fractional_amount(credit_note, data)
 		credit_note.submit()
 
 		self.consolidated_credit_note = credit_note.name
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 5930aa0..89f7f18 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -5,6 +5,7 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import change_settings
 
 from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
 from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
@@ -280,3 +281,100 @@
 			frappe.set_user("Administrator")
 			frappe.db.sql("delete from `tabPOS Profile`")
 			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	@change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3})
+	def test_consolidation_round_off_error_3(self):
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+			init_user_and_profile()
+
+			item_rates = [69, 59, 29]
+			for i in [1, 2]:
+				inv = create_pos_invoice(is_return=1, do_not_save=1)
+				inv.items = []
+				for rate in item_rates:
+					inv.append("items", {
+						"item_code": "_Test Item",
+						"warehouse": "_Test Warehouse - _TC",
+						"qty": -1,
+						"rate": rate,
+						"income_account": "Sales - _TC",
+						"expense_account": "Cost of Goods Sold - _TC",
+						"cost_center": "_Test Cost Center - _TC",
+					})
+				inv.append("taxes", {
+					"account_head": "_Test Account VAT - _TC",
+					"charge_type": "On Net Total",
+					"cost_center": "_Test Cost Center - _TC",
+					"description": "VAT",
+					"doctype": "Sales Taxes and Charges",
+					"rate": 15,
+					"included_in_print_rate": 1
+				})
+				inv.payments = []
+				inv.append('payments', {
+					'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157
+				})
+				inv.paid_amount = -157
+				inv.save()
+				inv.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.status, 'Return')
+			self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	def test_consolidation_rounding_adjustment(self):
+		'''
+		Test if the rounding adjustment is calculated correctly
+		'''
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+
+			init_user_and_profile()
+
+			inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
+			inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70
+			})
+			inv.insert()
+			inv.submit()
+
+			inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
+			inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60
+			})
+			inv2.insert()
+			inv2.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index b894f90..573da27 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -263,6 +263,9 @@
 		self.process_common_party_accounting()
 
 	def validate_pos_return(self):
+		if self.is_consolidated:
+			# pos return is already validated in pos invoice
+			return
 
 		if self.is_pos and self.is_return:
 			total_amount_in_payments = 0
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 07591e7..6d929e4 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1595,6 +1595,56 @@
 			self.assertEqual(expected_values[gle.account][1], gle.debit)
 			self.assertEqual(expected_values[gle.account][2], gle.credit)
 
+	def test_rounding_adjustment_3(self):
+		si = create_sales_invoice(do_not_save=True)
+		si.items = []
+		for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
+			si.append("items", {
+				"item_code": "_Test Item",
+				"gst_hsn_code": "999800",
+				"warehouse": "_Test Warehouse - _TC",
+				"qty": d[1],
+				"rate": d[0],
+				"income_account": "Sales - _TC",
+				"cost_center": "_Test Cost Center - _TC"
+			})
+		for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
+			si.append("taxes", {
+				"charge_type": "On Net Total",
+				"account_head": tax_account,
+				"description": tax_account,
+				"rate": 6,
+				"cost_center": "_Test Cost Center - _TC",
+				"included_in_print_rate": 1
+			})
+		si.save()
+		si.submit()
+		self.assertEqual(si.net_total, 4007.16)
+		self.assertEqual(si.grand_total, 4488.02)
+		self.assertEqual(si.total_taxes_and_charges, 480.86)
+		self.assertEqual(si.rounding_adjustment, -0.02)
+
+		expected_values = dict((d[0], d) for d in [
+			[si.debit_to, 4488.0, 0.0],
+			["_Test Account Service Tax - _TC", 0.0, 240.43],
+			["_Test Account VAT - _TC", 0.0, 240.43],
+			["Sales - _TC", 0.0, 4007.15],
+			["Round Off - _TC", 0.01, 0]
+		])
+
+		gl_entries = frappe.db.sql("""select account, debit, credit
+			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+			order by account asc""", si.name, as_dict=1)
+
+		debit_credit_diff = 0
+		for gle in gl_entries:
+			self.assertEqual(expected_values[gle.account][0], gle.account)
+			self.assertEqual(expected_values[gle.account][1], gle.debit)
+			self.assertEqual(expected_values[gle.account][2], gle.credit)
+			debit_credit_diff += (gle.debit - gle.credit)
+
+		self.assertEqual(debit_credit_diff, 0)
+
 	def test_sales_invoice_with_shipping_rule(self):
 		from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
 
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index 1d30934..8043a1b 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -55,5 +55,8 @@
 		frappe.throw(_("Disabled template must not be default template"))
 
 def validate_for_tax_category(doc):
+	if not doc.tax_category:
+		return
+
 	if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
 		frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index d24d56b..0cd5e86 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -274,7 +274,7 @@
 				debit_credit_diff += flt(d.credit)
 			round_off_account_exists = True
 
-	if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
+	if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
 		gl_map.remove(round_off_gle)
 		return
 
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index d554d52..3fe6b2d 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -68,6 +68,28 @@
 });
 
 frappe.ui.form.on('Asset Repair Consumed Item', {
+	item_code: function(frm, cdt, cdn) {
+		var item = locals[cdt][cdn];
+
+		let item_args = {
+			'item_code': item.item_code,
+			'warehouse': frm.doc.warehouse,
+			'qty': item.consumed_quantity,
+			'serial_no': item.serial_no,
+			'company': frm.doc.company
+		};
+
+		frappe.call({
+			method: 'erpnext.stock.utils.get_incoming_rate',
+			args: {
+				args: item_args
+			},
+			callback: function(r) {
+				frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message);
+			}
+		});
+	},
+
 	consumed_quantity: function(frm, cdt, cdn) {
 		var row = locals[cdt][cdn];
 		frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
index f63add1..4685a09 100644
--- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
@@ -13,12 +13,10 @@
  ],
  "fields": [
   {
-   "fetch_from": "item.valuation_rate",
    "fieldname": "valuation_rate",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Valuation Rate",
-   "read_only": 1
+   "label": "Valuation Rate"
   },
   {
    "fieldname": "consumed_quantity",
@@ -49,7 +47,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-11-11 18:23:00.492483",
+ "modified": "2022-02-08 17:37:20.028290",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Repair Consumed Item",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 1b5f35e..2e7d306 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -316,6 +316,16 @@
 			'target_ref_field': 'stock_qty',
 			'source_field': 'stock_qty'
 		})
+		self.status_updater.append({
+			'source_dt': 'Purchase Order Item',
+			'target_dt': 'Packed Item',
+			'target_field': 'ordered_qty',
+			'target_parent_dt': 'Sales Order',
+			'target_parent_field': '',
+			'join_field': 'sales_order_packed_item',
+			'target_ref_field': 'qty',
+			'source_field': 'stock_qty'
+		})
 
 	def update_delivered_qty_in_sales_order(self):
 		"""Update delivered qty in Sales Order for drop ship"""
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 87cd575..a18c527 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -63,6 +63,7 @@
   "material_request_item",
   "sales_order",
   "sales_order_item",
+  "sales_order_packed_item",
   "supplier_quotation",
   "supplier_quotation_item",
   "col_break5",
@@ -837,21 +838,30 @@
    "label": "Product Bundle",
    "options": "Product Bundle",
    "read_only": 1
+  },
+  {
+   "fieldname": "sales_order_packed_item",
+   "fieldtype": "Data",
+   "label": "Sales Order Packed Item",
+   "no_copy": 1,
+   "print_hide": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-08-30 20:06:26.712097",
+ "modified": "2022-02-02 13:10:18.398976",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "quick_entry": 1,
  "search_fields": "item_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index f98e5f1..60a8f92 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.functions import Coalesce, Sum
 from frappe.utils import date_diff, flt, getdate
 
 
@@ -16,12 +17,9 @@
 	validate_filters(filters)
 
 	columns = get_columns(filters)
-	conditions = get_conditions(filters)
+	data = get_data(filters)
 
-	#get queried data
-	data = get_data(filters, conditions)
-
-	#prepare data for report and chart views
+	# prepare data for report and chart views
 	data, chart_data = prepare_data(data, filters)
 
 	return columns, data, None, chart_data
@@ -34,53 +32,70 @@
 	elif date_diff(to_date, from_date) < 0:
 		frappe.throw(_("To Date cannot be before From Date."))
 
-def get_conditions(filters):
-	conditions = ''
+def get_data(filters):
+	mr = frappe.qb.DocType("Material Request")
+	mr_item = frappe.qb.DocType("Material Request Item")
 
+	query = (
+		frappe.qb.from_(mr)
+		.join(mr_item).on(mr_item.parent == mr.name)
+		.select(
+			mr.name.as_("material_request"),
+			mr.transaction_date.as_("date"),
+			mr_item.schedule_date.as_("required_date"),
+			mr_item.item_code.as_("item_code"),
+			Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
+			Coalesce(mr_item.stock_uom, '').as_("uom"),
+			Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
+			Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
+			(
+				Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))
+			).as_("qty_to_receive"),
+			Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
+			(
+				Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.ordered_qty, 0))
+			).as_("qty_to_order"),
+			mr_item.item_name,
+			mr_item.description,
+			mr.company
+		).where(
+			(mr.material_request_type == "Purchase")
+			& (mr.docstatus == 1)
+			& (mr.status != "Stopped")
+			& (mr.per_received < 100)
+		)
+	)
+
+	query = get_conditions(filters, query, mr, mr_item) # add conditional conditions
+
+	query = (
+		query.groupby(
+			mr.name, mr_item.item_code
+		).orderby(
+			mr.transaction_date, mr.schedule_date
+		)
+	)
+	data = query.run(as_dict=True)
+	return data
+
+def get_conditions(filters, query, mr, mr_item):
 	if filters.get("from_date") and filters.get("to_date"):
-		conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_date"),filters.get("to_date"))
-
+		query = (
+			query.where(
+				(mr.transaction_date >= filters.get("from_date"))
+				& (mr.transaction_date <= filters.get("to_date"))
+			)
+		)
 	if filters.get("company"):
-		conditions += " and mr.company = '{0}'".format(filters.get("company"))
+		query = query.where(mr.company == filters.get("company"))
 
 	if filters.get("material_request"):
-		conditions += " and mr.name = '{0}'".format(filters.get("material_request"))
+		query = query.where(mr.name == filters.get("material_request"))
 
 	if filters.get("item_code"):
-		conditions += " and mr_item.item_code = '{0}'".format(filters.get("item_code"))
+		query = query.where(mr_item.item_code == filters.get("item_code"))
 
-	return conditions
-
-def get_data(filters, conditions):
-	data = frappe.db.sql("""
-		select
-			mr.name as material_request,
-			mr.transaction_date as date,
-			mr_item.schedule_date as required_date,
-			mr_item.item_code as item_code,
-			sum(ifnull(mr_item.stock_qty, 0)) as qty,
-			ifnull(mr_item.stock_uom, '') as uom,
-			sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty,
-			sum(ifnull(mr_item.received_qty, 0)) as received_qty,
-			(sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as qty_to_receive,
-			(sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
-			mr_item.item_name as item_name,
-			mr_item.description as "description",
-			mr.company as company
-		from
-			`tabMaterial Request` mr, `tabMaterial Request Item` mr_item
-		where
-			mr_item.parent = mr.name
-			and mr.material_request_type = "Purchase"
-			and mr.docstatus = 1
-			and mr.status != "Stopped"
-			{conditions}
-		group by mr.name, mr_item.item_code
-		having
-			sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))
-		order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1)
-
-	return data
+	return query
 
 def update_qty_columns(row_to_update, data_row):
 	fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
new file mode 100644
index 0000000..f3c751c
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, today
+
+from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
+from erpnext.buying.report.requested_items_to_order_and_receive.requested_items_to_order_and_receive import (
+	get_data,
+)
+from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.material_request.material_request import make_purchase_order
+
+
+class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
+	def setUp(self) -> None:
+		create_item("Test MR Report Item")
+		self.setup_material_request() # to order and receive
+		self.setup_material_request(order=True) # to receive (ordered)
+		self.setup_material_request(order=True, receive=True) # complete (ordered & received)
+
+		self.filters = frappe._dict(
+			company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
+			item_code="Test MR Report Item"
+		)
+
+	def tearDown(self) -> None:
+		frappe.db.rollback()
+
+	def test_date_range(self):
+		data = get_data(self.filters)
+		self.assertEqual(len(data), 2) # MRs today should be fetched
+
+		self.filters.from_date = add_days(today(), 1)
+		data = get_data(self.filters)
+		self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is tomorrow
+
+	def test_ordered_received_material_requests(self):
+		data = get_data(self.filters)
+
+		# from the 3 MRs made, only 2 (to receive) should be fetched
+		self.assertEqual(len(data), 2)
+		self.assertEqual(data[0].ordered_qty, 0.0)
+		self.assertEqual(data[1].ordered_qty, 57.0)
+
+	def setup_material_request(self, order=False, receive=False):
+		po = None
+		test_records = frappe.get_test_records('Material Request')
+
+		mr = frappe.copy_doc(test_records[0])
+		mr.transaction_date = today()
+		mr.schedule_date = add_days(today(), 1)
+		for row in mr.items:
+			row.item_code = "Test MR Report Item"
+			row.item_name = "Test MR Report Item"
+			row.description = "Test MR Report Item"
+			row.uom = "Nos"
+			row.schedule_date = add_days(today(), 1)
+		mr.submit()
+
+		if order or receive:
+			po = make_purchase_order(mr.name)
+			po.supplier = "_Test Supplier"
+			po.submit()
+			if receive:
+				pr = make_purchase_receipt(po.name)
+				pr.submit()
+
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index c8e5edd..8972c32 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -507,13 +507,41 @@
 			"voucher_no": self.name,
 			"company": self.company
 		})
-		if future_sle_exists(args):
+
+		if future_sle_exists(args) or repost_required_for_queue(self):
 			item_based_reposting =  cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
 			if item_based_reposting:
 				create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
 			else:
 				create_repost_item_valuation_entry(args)
 
+def repost_required_for_queue(doc: StockController) -> bool:
+	"""check if stock document contains repeated item-warehouse with queue based valuation.
+
+	if queue exists for repeated items then SLEs need to reprocessed in background again.
+	"""
+
+	consuming_sles = frappe.db.get_all("Stock Ledger Entry",
+		filters={
+			"voucher_type": doc.doctype,
+			"voucher_no": doc.name,
+			"actual_qty": ("<", 0),
+			"is_cancelled": 0
+		},
+		fields=["item_code", "warehouse", "stock_queue"]
+	)
+	item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
+
+	unique_item_warehouses = set(item_warehouses)
+
+	if len(unique_item_warehouses) == len(item_warehouses):
+		return False
+
+	for sle in consuming_sles:
+		if sle.stock_queue != "[]":  # using FIFO/LIFO valuation
+			return True
+	return False
+
 
 @frappe.whitelist()
 def make_quality_inspections(doctype, docname, items):
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 2776628..a1bb667 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -113,17 +113,24 @@
 			for item in self.doc.get("items"):
 				self.doc.round_floats_in(item)
 
+				if not item.rate:
+					item.rate = item.price_list_rate
+
 				if item.discount_percentage == 100:
 					item.rate = 0.0
 				elif item.price_list_rate:
-					if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
+					if item.pricing_rules or abs(item.discount_percentage) > 0:
 						item.rate = flt(item.price_list_rate *
 							(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
-						item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
-					elif item.discount_amount and item.pricing_rules:
+
+						if abs(item.discount_percentage) > 0:
+							item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
+
+					elif item.discount_amount or item.pricing_rules:
 						item.rate =  item.price_list_rate - item.discount_amount
 
-				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
+				if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
+					'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
 					item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
 					if flt(item.rate_with_margin) > 0:
 						item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
@@ -270,7 +277,8 @@
 			shipping_rule.apply(self.doc)
 
 	def calculate_taxes(self):
-		if not self.doc.get('is_consolidated'):
+		rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
+		if not rounding_adjustment_computed:
 			self.doc.rounding_adjustment = 0
 
 		# maintain actual tax rate based on idx
@@ -326,7 +334,7 @@
 					if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
 						and self.doc.discount_amount \
 						and self.doc.apply_discount_on == "Grand Total" \
-						and not self.doc.get('is_consolidated'):
+						and not rounding_adjustment_computed:
 							self.doc.rounding_adjustment = flt(self.doc.grand_total
 								- flt(self.doc.discount_amount) - tax.total,
 								self.doc.precision("rounding_adjustment"))
@@ -465,20 +473,22 @@
 					self.doc.total_net_weight += d.total_weight
 
 	def set_rounded_total(self):
-		if not self.doc.get('is_consolidated'):
-			if self.doc.meta.get_field("rounded_total"):
-				if self.doc.is_rounded_total_disabled():
-					self.doc.rounded_total = self.doc.base_rounded_total = 0
-					return
+		if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
+			return
 
-				self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
-					self.doc.currency, self.doc.precision("rounded_total"))
+		if self.doc.meta.get_field("rounded_total"):
+			if self.doc.is_rounded_total_disabled():
+				self.doc.rounded_total = self.doc.base_rounded_total = 0
+				return
 
-				#if print_in_rate is set, we would have already calculated rounding adjustment
-				self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
-					self.doc.precision("rounding_adjustment"))
+			self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
+				self.doc.currency, self.doc.precision("rounded_total"))
 
-				self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
+			#if print_in_rate is set, we would have already calculated rounding adjustment
+			self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
+				self.doc.precision("rounding_adjustment"))
+
+			self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
 
 	def _cleanup(self):
 		if not self.doc.get('is_consolidated'):
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index b1eaaf8..b1e373e 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -174,16 +174,22 @@
 def get_unmarked_days(employee, month, exclude_holidays=0):
 	import calendar
 	month_map = get_month_map()
-
 	today = get_datetime()
 
-	dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(1, calendar.monthrange(today.year, month_map[month])[1] + 1)]
+	joining_date, relieving_date = frappe.get_cached_value("Employee", employee, ["date_of_joining", "relieving_date"])
+	start_day = 1
+	end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
 
-	length = len(dates_of_month)
-	month_start, month_end = dates_of_month[0], dates_of_month[length-1]
+	if joining_date and joining_date.month == month_map[month]:
+		start_day = joining_date.day
 
+	if relieving_date and relieving_date.month == month_map[month]:
+		end_day = relieving_date.day + 1
 
-	records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [
+	dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(start_day, end_day)]
+	month_start, month_end = dates_of_month[0], dates_of_month[-1]
+
+	records = frappe.get_all("Attendance", fields=['attendance_date', 'employee'], filters=[
 		["attendance_date", ">=", month_start],
 		["attendance_date", "<=", month_end],
 		["employee", "=", employee],
@@ -200,7 +206,7 @@
 
 	for date in dates_of_month:
 		date_time = get_datetime(date)
-		if today.day == date_time.day and today.month == date_time.month:
+		if today.day <= date_time.day and today.month <= date_time.month:
 			break
 		if date_time not in marked_days:
 			unmarked_days.append(date)
diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py
index a770d70..c74967d 100644
--- a/erpnext/hr/doctype/attendance/test_attendance.py
+++ b/erpnext/hr/doctype/attendance/test_attendance.py
@@ -1,20 +1,108 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
 # See license.txt
 
-import unittest
-
 import frappe
-from frappe.utils import nowdate
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, get_first_day, getdate, now_datetime, nowdate
+
+from erpnext.hr.doctype.attendance.attendance import (
+	get_month_map,
+	get_unmarked_days,
+	mark_attendance,
+)
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
 
 test_records = frappe.get_test_records('Attendance')
 
-class TestAttendance(unittest.TestCase):
+class TestAttendance(FrappeTestCase):
 	def test_mark_absent(self):
-		from erpnext.hr.doctype.employee.test_employee import make_employee
 		employee = make_employee("test_mark_absent@example.com")
 		date = nowdate()
 		frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
-		from erpnext.hr.doctype.attendance.attendance import mark_attendance
 		attendance = mark_attendance(employee, date, 'Absent')
 		fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
 		self.assertEqual(attendance, fetch_attendance)
+
+	def test_unmarked_days(self):
+		first_day = get_first_day(getdate())
+
+		employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
+		frappe.db.delete('Attendance', {'employee': employee})
+
+		from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+		holiday_list = make_holiday_list()
+		frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
+
+		first_sunday = get_first_sunday(holiday_list)
+		mark_attendance(employee, first_day, 'Present')
+		month_name = get_month_name(first_day)
+
+		unmarked_days = get_unmarked_days(employee, month_name)
+		unmarked_days = [getdate(date) for date in unmarked_days]
+
+		# attendance already marked for the day
+		self.assertNotIn(first_day, unmarked_days)
+		# attendance unmarked
+		self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
+		# holiday considered in unmarked days
+		self.assertIn(first_sunday, unmarked_days)
+
+	def test_unmarked_days_excluding_holidays(self):
+		first_day = get_first_day(getdate())
+
+		employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
+		frappe.db.delete('Attendance', {'employee': employee})
+
+		from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+		holiday_list = make_holiday_list()
+		frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
+
+		first_sunday = get_first_sunday(holiday_list)
+		mark_attendance(employee, first_day, 'Present')
+		month_name = get_month_name(first_day)
+
+		unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
+		unmarked_days = [getdate(date) for date in unmarked_days]
+
+		# attendance already marked for the day
+		self.assertNotIn(first_day, unmarked_days)
+		# attendance unmarked
+		self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
+		# holidays not considered in unmarked days
+		self.assertNotIn(first_sunday, unmarked_days)
+
+	def test_unmarked_days_as_per_joining_and_relieving_dates(self):
+		now = now_datetime()
+		previous_month = now.month - 1
+		first_day = now.replace(day=1).replace(month=previous_month).date()
+
+		doj = add_days(first_day, 1)
+		relieving_date = add_days(first_day, 5)
+		employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
+			relieving_date=relieving_date)
+		frappe.db.delete('Attendance', {'employee': employee})
+
+		attendance_date = add_days(first_day, 2)
+		mark_attendance(employee, attendance_date, 'Present')
+		month_name = get_month_name(first_day)
+
+		unmarked_days = get_unmarked_days(employee, month_name)
+		unmarked_days = [getdate(date) for date in unmarked_days]
+
+		# attendance already marked for the day
+		self.assertNotIn(attendance_date, unmarked_days)
+		# date before doj not in unmarked days
+		self.assertNotIn(add_days(doj, -1), unmarked_days)
+		# date after relieving not in unmarked days
+		self.assertNotIn(add_days(relieving_date, 1), unmarked_days)
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+
+def get_month_name(date):
+	month_number = date.month
+	for month, number in get_month_map().items():
+		if number == month_number:
+			return month
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index 0475453..b050183 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_import": 1,
  "autoname": "naming_series:",
- "creation": "2017-10-09 14:26:29.612365",
+ "creation": "2022-01-17 18:36:51.450395",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -121,7 +121,7 @@
    "fieldtype": "Select",
    "label": "Status",
    "no_copy": 1,
-   "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
+   "options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled",
    "read_only": 1
   },
   {
@@ -200,7 +200,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-11 18:38:38.617478",
+ "modified": "2022-01-17 19:33:52.345823",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Advance",
@@ -237,5 +237,41 @@
  "search_fields": "employee,employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [
+  {
+   "color": "Red",
+   "custom": 1,
+   "title": "Draft"
+  },
+  {
+   "color": "Green",
+   "custom": 1,
+   "title": "Paid"
+  },
+  {
+   "color": "Orange",
+   "custom": 1,
+   "title": "Unpaid"
+  },
+  {
+   "color": "Blue",
+   "custom": 1,
+   "title": "Claimed"
+  },
+  {
+   "color": "Gray",
+   "title": "Returned"
+  },
+  {
+   "color": "Yellow",
+   "title": "Partly Claimed and Returned"
+  },
+  {
+   "color": "Red",
+   "custom": 1,
+   "title": "Cancelled"
+  }
+ ],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 7aac2b6..79d389d 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -27,19 +27,33 @@
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ('GL Entry')
+		self.set_status(update=True)
 
-	def set_status(self):
+	def set_status(self, update=False):
+		precision = self.precision("paid_amount")
+		total_amount = flt(flt(self.claimed_amount) + flt(self.return_amount), precision)
+		status = None
+
 		if self.docstatus == 0:
-			self.status = "Draft"
-		if self.docstatus == 1:
-			if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount):
-				self.status = "Claimed"
-			elif self.paid_amount and self.advance_amount == flt(self.paid_amount):
-				self.status = "Paid"
+			status = "Draft"
+		elif self.docstatus == 1:
+			if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
+				status = "Claimed"
+			elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
+				status = "Returned"
+			elif flt(self.claimed_amount) > 0 and (flt(self.return_amount) > 0) and total_amount == flt(self.paid_amount, precision):
+				status = "Partly Claimed and Returned"
+			elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(self.paid_amount, precision):
+				status = "Paid"
 			else:
-				self.status = "Unpaid"
+				status = "Unpaid"
 		elif self.docstatus == 2:
-			self.status = "Cancelled"
+			status = "Cancelled"
+
+		if update:
+			self.db_set("status", status)
+		else:
+			self.status = status
 
 	def set_total_advance_paid(self):
 		gle = frappe.qb.DocType("GL Entry")
@@ -85,9 +99,7 @@
 
 		self.db_set("paid_amount", paid_amount)
 		self.db_set("return_amount", return_amount)
-		self.set_status()
-		frappe.db.set_value("Employee Advance", self.name , "status", self.status)
-
+		self.set_status(update=True)
 
 	def update_claimed_amount(self):
 		claimed_amount = frappe.db.sql("""
@@ -103,8 +115,8 @@
 
 		frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
 		self.reload()
-		self.set_status()
-		frappe.db.set_value("Employee Advance", self.name, "status", self.status)
+		self.set_status(update=True)
+
 
 @frappe.whitelist()
 def get_pending_amount(employee, posting_date):
@@ -222,7 +234,8 @@
 		'reference_name': employee_advance_name,
 		'party_type': 'Employee',
 		'party': employee,
-		'is_advance': 'Yes'
+		'is_advance': 'Yes',
+		'cost_center': erpnext.get_default_cost_center(company)
 	})
 
 	bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
@@ -233,7 +246,8 @@
 		"debit_in_account_currency": bank_amount,
 		"account_currency": bank_cash_account.account_currency,
 		"account_type": bank_cash_account.account_type,
-		"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
+		"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
+		"cost_center": erpnext.get_default_cost_center(company)
 	})
 
 	return je.as_dict()
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
index 5f2e720..e3c1487 100644
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py
@@ -4,7 +4,7 @@
 import unittest
 
 import frappe
-from frappe.utils import nowdate
+from frappe.utils import flt, nowdate
 
 import erpnext
 from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -12,12 +12,21 @@
 	EmployeeAdvanceOverPayment,
 	create_return_through_additional_salary,
 	make_bank_entry,
+	make_return_entry,
+)
+from erpnext.hr.doctype.expense_claim.expense_claim import get_advances
+from erpnext.hr.doctype.expense_claim.test_expense_claim import (
+	get_payable_account,
+	make_expense_claim,
 )
 from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
 
 
 class TestEmployeeAdvance(unittest.TestCase):
+	def setUp(self):
+		frappe.db.delete("Employee Advance")
+
 	def test_paid_amount_and_status(self):
 		employee_name = make_employee("_T@employe.advance")
 		advance = make_employee_advance(employee_name)
@@ -52,9 +61,102 @@
 		self.assertEqual(advance.paid_amount, 0)
 		self.assertEqual(advance.status, "Unpaid")
 
+		advance.cancel()
+		advance.reload()
+		self.assertEqual(advance.status, "Cancelled")
+
+	def test_claimed_status(self):
+		# CLAIMED Status check, full amount claimed
+		payable_account = get_payable_account("_Test Company")
+		claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		claim = get_advances_for_claim(claim, advance.name)
+		claim.save()
+		claim.submit()
+
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 1000)
+		self.assertEqual(advance.status, "Claimed")
+
+		# advance should not be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name not in advances)
+
+		# cancel claim; status should be Paid
+		claim.cancel()
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 0)
+		self.assertEqual(advance.status, "Paid")
+
+	def test_partly_claimed_and_returned_status(self):
+		payable_account = get_payable_account("_Test Company")
+		claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		# PARTLY CLAIMED AND RETURNED status check
+		# 500 Claimed, 500 Returned
+		claim = make_expense_claim(payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+
+		advance = make_employee_advance(claim.employee)
+		pe = make_payment_entry(advance)
+		pe.submit()
+
+		claim = get_advances_for_claim(claim, advance.name, amount=500)
+		claim.save()
+		claim.submit()
+
+		advance.reload()
+		self.assertEqual(advance.claimed_amount, 500)
+		self.assertEqual(advance.status, "Paid")
+
+		entry = make_return_entry(
+			employee=advance.employee,
+			company=advance.company,
+			employee_advance_name=advance.name,
+			return_amount=flt(advance.paid_amount - advance.claimed_amount),
+			advance_account=advance.advance_account,
+			mode_of_payment=advance.mode_of_payment,
+			currency=advance.currency,
+			exchange_rate=advance.exchange_rate
+		)
+
+		entry = frappe.get_doc(entry)
+		entry.insert()
+		entry.submit()
+
+		advance.reload()
+		self.assertEqual(advance.return_amount, 500)
+		self.assertEqual(advance.status, "Partly Claimed and Returned")
+
+		# advance should not be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name not in advances)
+
+		# Cancel return entry; status should change to PAID
+		entry.cancel()
+		advance.reload()
+		self.assertEqual(advance.return_amount, 0)
+		self.assertEqual(advance.status, "Paid")
+
+		# advance should be shown in claims
+		advances = get_advances(claim.employee)
+		advances = [entry.name for entry in advances]
+		self.assertTrue(advance.name in advances)
+
 	def test_repay_unclaimed_amount_from_salary(self):
 		employee_name = make_employee("_T@employe.advance")
 		advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
+		pe = make_payment_entry(advance)
+		pe.submit()
 
 		args = {"type": "Deduction"}
 		create_salary_component("Advance Salary - Deduction", **args)
@@ -82,11 +184,13 @@
 
 		advance.reload()
 		self.assertEqual(advance.return_amount, 1000)
+		self.assertEqual(advance.status, "Returned")
 
 		# update advance return amount on additional salary cancellation
 		additional_salary.cancel()
 		advance.reload()
 		self.assertEqual(advance.return_amount, 700)
+		self.assertEqual(advance.status, "Paid")
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -118,3 +222,24 @@
 	doc.submit()
 
 	return doc
+
+
+def get_advances_for_claim(claim, advance_name, amount=None):
+	advances = get_advances(claim.employee, advance_name)
+
+	for entry in advances:
+		if amount:
+			allocated_amount = amount
+		else:
+			allocated_amount = flt(entry.paid_amount) - flt(entry.claimed_amount)
+
+		claim.append("advances", {
+			"employee_advance": entry.name,
+			"posting_date": entry.posting_date,
+			"advance_account": entry.advance_account,
+			"advance_paid": entry.paid_amount,
+			"unclaimed_amount": allocated_amount,
+			"allocated_amount": allocated_amount
+		})
+
+	return claim
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js
index 0479457..af80b63 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.js
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.js
@@ -171,7 +171,7 @@
 					['docstatus', '=', 1],
 					['employee', '=', frm.doc.employee],
 					['paid_amount', '>', 0],
-					['status', '!=', 'Claimed']
+					['status', 'not in', ['Claimed', 'Returned', 'Partly Claimed and Returned']]
 				]
 			};
 		});
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 7e3898b..fe04efb 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -23,10 +23,10 @@
 
 	def validate(self):
 		validate_active_employee(self.employee)
-		self.validate_advances()
+		set_employee_name(self)
 		self.validate_sanctioned_amount()
 		self.calculate_total_amount()
-		set_employee_name(self)
+		self.validate_advances()
 		self.set_expense_account(validate=True)
 		self.set_payable_account()
 		self.set_cost_center()
@@ -341,18 +341,27 @@
 
 @frappe.whitelist()
 def get_advances(employee, advance_id=None):
-	if not advance_id:
-		condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount'.format(frappe.db.escape(employee))
-	else:
-		condition = 'name={0}'.format(frappe.db.escape(advance_id))
+	advance = frappe.qb.DocType("Employee Advance")
 
-	return frappe.db.sql("""
-		select
-			name, posting_date, paid_amount, claimed_amount, advance_account
-		from
-			`tabEmployee Advance`
-		where {0}
-	""".format(condition), as_dict=1)
+	query = (
+		frappe.qb.from_(advance)
+		.select(
+			advance.name, advance.posting_date, advance.paid_amount,
+			advance.claimed_amount, advance.advance_account
+		)
+	)
+
+	if not advance_id:
+		query = query.where(
+			(advance.docstatus == 1)
+			& (advance.employee == employee)
+			& (advance.paid_amount > 0)
+			& (advance.status.notin(["Claimed", "Returned", "Partly Claimed and Returned"]))
+		)
+	else:
+		query = query.where(advance.name == advance_id)
+
+	return query.run(as_dict=True)
 
 
 @frappe.whitelist()
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 70250f5..ef5f4bc 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -4,6 +4,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.functions import Max, Min, Sum
 from frappe.utils import (
 	add_days,
 	cint,
@@ -567,28 +568,39 @@
 	return get_remaining_leaves(allocation, leaves_taken, date, expiry)
 
 def get_leave_allocation_records(employee, date, leave_type=None):
-	''' returns the total allocated leaves and carry forwarded leaves based on ledger entries '''
+	"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
+	Ledger = frappe.qb.DocType("Leave Ledger Entry")
 
-	conditions = ("and leave_type='%s'" % leave_type) if leave_type else ""
-	allocation_details = frappe.db.sql("""
-		SELECT
-			SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves,
-			SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves,
-			MIN(from_date) as from_date,
-			MAX(to_date) as to_date,
-			leave_type
-		FROM `tabLeave Ledger Entry`
-		WHERE
-			from_date <= %(date)s
-			AND to_date >= %(date)s
-			AND docstatus=1
-			AND transaction_type="Leave Allocation"
-			AND employee=%(employee)s
-			AND is_expired=0
-			AND is_lwp=0
-			{0}
-		GROUP BY employee, leave_type
-	""".format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec
+	cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
+	sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")
+
+	new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
+	sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")
+
+	query = (
+		frappe.qb.from_(Ledger)
+		.select(
+			sum_cf_leaves,
+			sum_new_leaves,
+			Min(Ledger.from_date).as_("from_date"),
+			Max(Ledger.to_date).as_("to_date"),
+			Ledger.leave_type
+		).where(
+			(Ledger.from_date <= date)
+			& (Ledger.to_date >= date)
+			& (Ledger.docstatus == 1)
+			& (Ledger.transaction_type == "Leave Allocation")
+			& (Ledger.employee == employee)
+			& (Ledger.is_expired == 0)
+			& (Ledger.is_lwp == 0)
+		)
+	)
+
+	if leave_type:
+		query = query.where((Ledger.leave_type == leave_type))
+	query = query.groupby(Ledger.employee, Ledger.leave_type)
+
+	allocation_details = query.run(as_dict=True)
 
 	allocated_leaves = frappe._dict()
 	for d in allocation_details:
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 6d27f4a..7b3aa49 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -792,4 +792,4 @@
 		order by holiday_date
 	""", (holiday_list, month_start_date, month_end_date))[0][0]
 
-	return first_sunday
\ No newline at end of file
+	return first_sunday
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index d640f3f..37d2b9f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -918,7 +918,7 @@
 			frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
 
 @frappe.whitelist()
-def get_children(doctype, parent=None, is_root=False, **filters):
+def get_children(parent=None, is_root=False, **filters):
 	if not parent or parent=="BOM":
 		frappe.msgprint(_('Please select a BOM'))
 		return
diff --git a/erpnext/manufacturing/doctype/operation/operation_dashboard.py b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
index 4fbcf49..9f7efa2 100644
--- a/erpnext/manufacturing/doctype/operation/operation_dashboard.py
+++ b/erpnext/manufacturing/doctype/operation/operation_dashboard.py
@@ -7,7 +7,7 @@
 		'transactions': [
 			{
 				'label': _('Manufacture'),
-				'items': ['BOM', 'Work Order', 'Job Card', 'Timesheet']
+				'items': ['BOM', 'Work Order', 'Job Card']
 			}
 		]
 	}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index c33e646..e8759f5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -232,7 +232,7 @@
 		});
 	},
 	combine_items: function (frm) {
-		frm.clear_table('prod_plan_references');
+		frm.clear_table("prod_plan_references");
 
 		frappe.call({
 			method: "get_items",
@@ -247,6 +247,13 @@
 		});
 	},
 
+	combine_sub_items: (frm) => {
+		if (frm.doc.sub_assembly_items.length > 0) {
+			frm.clear_table("sub_assembly_items");
+			frm.trigger("get_sub_assembly_items");
+		}
+	},
+
 	get_sub_assembly_items: function(frm) {
 		frm.dirty();
 
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 56cf2b4..3bfb764 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -36,6 +36,7 @@
   "prod_plan_references",
   "section_break_24",
   "get_sub_assembly_items",
+  "combine_sub_items",
   "sub_assembly_items",
   "material_request_planning",
   "include_non_stock_items",
@@ -340,7 +341,6 @@
   {
    "fieldname": "prod_plan_references",
    "fieldtype": "Table",
-   "hidden": 1,
    "label": "Production Plan Item Reference",
    "options": "Production Plan Item Reference"
   },
@@ -370,16 +370,23 @@
    "fieldname": "to_delivery_date",
    "fieldtype": "Date",
    "label": "To Delivery Date"
+  },
+  {
+   "default": "0",
+   "fieldname": "combine_sub_items",
+   "fieldtype": "Check",
+   "label": "Consolidate Sub Assembly Items"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-06 18:35:59.642232",
+ "modified": "2022-02-23 17:16:10.629378",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 80003da..48cd753 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -21,7 +21,8 @@
 )
 from frappe.utils.csvutils import build_csv_response
 
-from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no
+from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_children
+from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
 from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 
@@ -570,17 +571,28 @@
 
 	@frappe.whitelist()
 	def get_sub_assembly_items(self, manufacturing_type=None):
+		"Fetch sub assembly items and optionally combine them."
 		self.sub_assembly_items = []
+		sub_assembly_items_store = [] # temporary store to process all subassembly items
+
 		for row in self.po_items:
 			bom_data = []
 			get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
 			self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
+			sub_assembly_items_store.extend(bom_data)
 
-		self.sub_assembly_items.sort(key= lambda d: d.bom_level, reverse=True)
-		for idx, row in enumerate(self.sub_assembly_items, start=1):
-			row.idx = idx
+		if self.combine_sub_items:
+			# Combine subassembly items
+			sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
+
+		sub_assembly_items_store.sort(key= lambda d: d.bom_level, reverse=True) # sort by bom level
+
+		for idx, row in enumerate(sub_assembly_items_store):
+			row.idx = idx + 1
+			self.append("sub_assembly_items", row)
 
 	def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
+		"Modify bom_data, set additional details."
 		for data in bom_data:
 			data.qty = data.stock_qty
 			data.production_plan_item = row.name
@@ -589,7 +601,32 @@
 			data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
 				else "In House")
 
-			self.append("sub_assembly_items", data)
+	def combine_subassembly_items(self, sub_assembly_items_store):
+		"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
+		key_wise_data = {}
+		for row in sub_assembly_items_store:
+			key = (
+				row.get("production_item"), row.get("fg_warehouse"),
+				row.get("bom_no"), row.get("type_of_manufacturing")
+			)
+			if key not in key_wise_data:
+				# intialise (item, wh, bom no, man.g type) wise dict
+				key_wise_data[key] = row
+				continue
+
+			existing_row = key_wise_data[key]
+			if existing_row:
+				# if row with same (item, wh, bom no, man.g type) key, merge
+				existing_row.qty += flt(row.qty)
+				existing_row.stock_qty += flt(row.stock_qty)
+				existing_row.bom_level = max(existing_row.bom_level, row.bom_level)
+				continue
+			else:
+				# add row with key
+				key_wise_data[key] = row
+
+		sub_assembly_items_store = [key_wise_data[key] for key in key_wise_data] # unpack into single level list
+		return sub_assembly_items_store
 
 	def all_items_completed(self):
 		all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001
@@ -1031,7 +1068,7 @@
 	}
 
 def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
-	data = get_children('BOM', parent = bom_no)
+	data = get_bom_children(parent=bom_no)
 	for d in data:
 		if d.expandable:
 			parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index b2a41ff..eeab788 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -38,6 +38,9 @@
 			if not frappe.db.get_value('BOM', {'item': item}):
 				make_bom(item = item, raw_materials = raw_materials)
 
+	def tearDown(self) -> None:
+		frappe.db.rollback()
+
 	def test_production_plan_mr_creation(self):
 		"Test if MRs are created for unavailable raw materials."
 		pln = create_production_plan(item_code='Test Production Item 1')
@@ -110,7 +113,7 @@
 			item_code='Test Production Item 1',
 			ignore_existing_ordered_qty=1
 		)
-		self.assertTrue(len(pln.mr_items), 1)
+		self.assertTrue(len(pln.mr_items))
 		self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
 
 		sr1.cancel()
@@ -151,7 +154,7 @@
 			use_multi_level_bom=0,
 			ignore_existing_ordered_qty=0
 		)
-		self.assertTrue(len(pln.mr_items), 0)
+		self.assertFalse(len(pln.mr_items))
 
 		sr1.cancel()
 		sr2.cancel()
@@ -258,6 +261,51 @@
 		pln.reload()
 		pln.cancel()
 
+	def test_production_plan_combine_subassembly(self):
+		"""
+		Test combining Sub assembly items belonging to the same BOM in Prod Plan.
+		1) Red-Car -> Wheel (sub assembly) > BOM-WHEEL-001
+		2) Green-Car -> Wheel (sub assembly) > BOM-WHEEL-001
+		"""
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		bom_tree_1 = {
+			"Red-Car": {"Wheel": {"Rubber": {}}}
+		}
+		bom_tree_2 = {
+			"Green-Car": {"Wheel": {"Rubber": {}}}
+		}
+
+		parent_bom_1 = create_nested_bom(bom_tree_1, prefix="")
+		parent_bom_2 = create_nested_bom(bom_tree_2, prefix="")
+
+		# make sure both boms use same subassembly bom
+		subassembly_bom = parent_bom_1.items[0].bom_no
+		frappe.db.set_value("BOM Item", parent_bom_2.items[0].name, "bom_no", subassembly_bom)
+
+		plan = create_production_plan(item_code="Red-Car", use_multi_level_bom=1, do_not_save=True)
+		plan.append("po_items", { # Add Green-Car to Prod Plan
+			'use_multi_level_bom': 1,
+			'item_code': "Green-Car",
+			'bom_no': frappe.db.get_value('Item', "Green-Car", 'default_bom'),
+			'planned_qty': 1,
+			'planned_start_date': now_datetime()
+		})
+		plan.get_sub_assembly_items()
+		self.assertTrue(len(plan.sub_assembly_items), 2)
+
+		plan.combine_sub_items = 1
+		plan.get_sub_assembly_items()
+
+		self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
+		self.assertEqual(plan.sub_assembly_items[0].qty, 2.0)
+		self.assertEqual(plan.sub_assembly_items[0].stock_qty, 2.0)
+
+		# change warehouse in one row, sub-assemblies should not merge
+		plan.po_items[0].warehouse = "Finished Goods - _TC"
+		plan.get_sub_assembly_items()
+		self.assertTrue(len(plan.sub_assembly_items), 2)
+
 	def test_pp_to_mr_customer_provided(self):
 		" Test Material Request from Production Plan for Customer Provided Item."
 		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
@@ -532,6 +580,7 @@
 			wip_warehouse='Work In Progress - _TC',
 			fg_warehouse='Finished Goods - _TC',
 			skip_transfer=1,
+			use_multi_level_bom=1,
 			do_not_submit=True
 		)
 		wo.production_plan = pln.name
@@ -576,6 +625,7 @@
 			wip_warehouse='Work In Progress - _TC',
 			fg_warehouse='Finished Goods - _TC',
 			skip_transfer=1,
+			use_multi_level_bom=1,
 			do_not_submit=True
 		)
 		wo.production_plan = pln.name
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 33a313e..b480c70 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -17,7 +17,7 @@
 	},
 
 	calculate_operating_cost: function(frm, child) {
-		const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
+		const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, precision("operating_cost", child));
 		frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
 	}
 });
diff --git a/erpnext/manufacturing/doctype/routing/routing.py b/erpnext/manufacturing/doctype/routing/routing.py
index 1c76634..b207906 100644
--- a/erpnext/manufacturing/doctype/routing/routing.py
+++ b/erpnext/manufacturing/doctype/routing/routing.py
@@ -20,7 +20,8 @@
 		for operation in self.operations:
 			if not operation.hour_rate:
 				operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate')
-			operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60, 2)
+			operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
+					operation.precision("operating_cost"))
 
 	def set_routing_id(self):
 		sequence_id = 0
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 549ec7b..bc07d22 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1040,7 +1040,7 @@
 	wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
 	wo_order.company = args.company or "_Test Company"
 	wo_order.stock_uom = args.stock_uom or "_Test UOM"
-	wo_order.use_multi_level_bom=0
+	wo_order.use_multi_level_bom= args.use_multi_level_bom or 0
 	wo_order.skip_transfer=args.skip_transfer or 0
 	wo_order.get_items_and_operations_from_bom()
 	wo_order.sales_order = args.sales_order or None
diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
index bc481ca..1fa1494 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py
@@ -11,9 +11,9 @@
 			},
 			{
 				'label': _('Transaction'),
-				'items': ['Work Order', 'Job Card', 'Timesheet']
+				'items': ['Work Order', 'Job Card',]
 			}
 		],
 		'disable_create_buttons': ['BOM', 'Routing', 'Operation',
-			'Work Order', 'Job Card', 'Timesheet']
+			'Work Order', 'Job Card',]
 	}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 12af8f8..13f0e7b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -356,4 +356,5 @@
 erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
 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
\ No newline at end of file
+erpnext.patches.v14_0.delete_non_profit_doctypes
+erpnext.patches.v14_0.update_employee_advance_status
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/update_employee_advance_status.py b/erpnext/patches/v14_0/update_employee_advance_status.py
new file mode 100644
index 0000000..a20e35a
--- /dev/null
+++ b/erpnext/patches/v14_0/update_employee_advance_status.py
@@ -0,0 +1,26 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc('hr', 'doctype', 'employee_advance')
+
+	advance = frappe.qb.DocType('Employee Advance')
+	(frappe.qb
+		.update(advance)
+		.set(advance.status, 'Returned')
+		.where(
+			(advance.docstatus == 1)
+			& ((advance.return_amount) & (advance.paid_amount == advance.return_amount))
+			& (advance.status == 'Paid')
+		)
+	).run()
+
+	(frappe.qb
+		.update(advance)
+		.set(advance.status, 'Partly Claimed and Returned')
+		.where(
+			(advance.docstatus == 1)
+			& ((advance.claimed_amount & advance.return_amount) & (advance.paid_amount == (advance.return_amount + advance.claimed_amount)))
+			& (advance.status == 'Paid')
+		)
+	).run()
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index bf8bd05..d618568 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -105,6 +105,8 @@
 				return_amount += self.amount
 
 			frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount)
+			advance = frappe.get_doc("Employee Advance", self.ref_docname)
+			advance.set_status(update=True)
 
 	def update_employee_referral(self, cancel=False):
 		if self.ref_doctype == "Employee Referral":
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index d2a3998..b44dbb9 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -307,28 +307,59 @@
 			if payroll_based_on == "Attendance":
 				self.payment_days -= flt(absent)
 
-			unmarked_days = self.get_unmarked_days()
 			consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
 
 			if payroll_based_on == "Attendance" and consider_unmarked_attendance_as =="Absent":
+				unmarked_days = self.get_unmarked_days(include_holidays_in_total_working_days)
 				self.absent_days += unmarked_days #will be treated as absent
 				self.payment_days -= unmarked_days
-				if include_holidays_in_total_working_days:
-					for holiday in holidays:
-						if not frappe.db.exists("Attendance", {"employee": self.employee, "attendance_date": holiday, "docstatus": 1 }):
-							self.payment_days += 1
 		else:
 			self.payment_days = 0
 
-	def get_unmarked_days(self):
-		marked_days = frappe.get_all("Attendance", filters = {
-					"attendance_date": ["between", [self.start_date, self.end_date]],
-					"employee": self.employee,
-					"docstatus": 1
-				}, fields = ["COUNT(*) as marked_days"])[0].marked_days
+	def get_unmarked_days(self, include_holidays_in_total_working_days):
+		unmarked_days = self.total_working_days
+		joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
+			["date_of_joining", "relieving_date"])
+		start_date = self.start_date
+		end_date = self.end_date
 
-		return self.total_working_days - marked_days
+		if joining_date and (getdate(self.start_date) < joining_date <= getdate(self.end_date)):
+			start_date = joining_date
+			unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(unmarked_days,
+				include_holidays_in_total_working_days, self.start_date, joining_date)
 
+		if relieving_date and (getdate(self.start_date) <= relieving_date < getdate(self.end_date)):
+			end_date = relieving_date
+			unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(unmarked_days,
+				include_holidays_in_total_working_days, relieving_date, self.end_date)
+
+		# exclude days for which attendance has been marked
+		unmarked_days -= frappe.get_all("Attendance", filters = {
+			"attendance_date": ["between", [start_date, end_date]],
+			"employee": self.employee,
+			"docstatus": 1
+		}, fields = ["COUNT(*) as marked_days"])[0].marked_days
+
+		return unmarked_days
+
+	def get_unmarked_days_based_on_doj_or_relieving(self, unmarked_days,
+		include_holidays_in_total_working_days, start_date, end_date):
+		"""
+		Exclude days before DOJ or after
+		Relieving Date from unmarked days
+		"""
+		from erpnext.hr.doctype.employee.employee import is_holiday
+
+		if include_holidays_in_total_working_days:
+			unmarked_days -= date_diff(end_date, start_date)
+		else:
+			# exclude only if not holidays
+			for days in range(date_diff(end_date, start_date)):
+				date = add_days(end_date, -days)
+				if not is_holiday(self.employee, date):
+					unmarked_days -= 1
+
+		return unmarked_days
 
 	def get_payment_days(self, joining_date, relieving_date, include_holidays_in_total_working_days):
 		if not joining_date:
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 6a5debf..fe15f2d 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -7,10 +7,12 @@
 
 import frappe
 from frappe.model.document import Document
+from frappe.tests.utils import change_settings
 from frappe.utils import (
 	add_days,
 	add_months,
 	cstr,
+	date_diff,
 	flt,
 	get_first_day,
 	get_last_day,
@@ -21,6 +23,7 @@
 
 import erpnext
 from erpnext.accounts.utils import get_fiscal_year
+from erpnext.hr.doctype.attendance.attendance import mark_attendance
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
@@ -37,17 +40,17 @@
 		setup_test()
 
 	def tearDown(self):
+		frappe.db.rollback()
 		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
 		frappe.set_user("Administrator")
 
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Attendance",
+		"daily_wages_fraction_for_half_day": 0.75
+	})
 	def test_payment_days_based_on_attendance(self):
-		from erpnext.hr.doctype.attendance.attendance import mark_attendance
 		no_of_days = self.get_no_of_days()
 
-		# Payroll based on attendance
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
-		frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75)
-
 		emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
 
@@ -85,14 +88,78 @@
 
 		self.assertEqual(ss.gross_pay, gross_pay)
 
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Attendance",
+		"consider_unmarked_attendance_as": "Absent",
+		"include_holidays_in_total_working_days": True
+	})
+	def test_payment_days_for_mid_joinee_including_holidays(self):
+		from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
 
+		no_of_days = self.get_no_of_days()
+		month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
+
+		new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
+		joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
+		frappe.db.set_value("Employee", new_emp_id, {
+			"date_of_joining": joining_date,
+			"relieving_date": relieving_date,
+			"status": "Left"
+		})
+
+		holidays = 0
+
+		for days in range(date_diff(relieving_date, joining_date) + 1):
+			date = add_days(joining_date, days)
+			if not is_holiday("Salary Slip Test Holiday List", date):
+				mark_attendance(new_emp_id, date, 'Present', ignore_validate=True)
+			else:
+				holidays += 1
+
+		new_ss = make_employee_salary_slip("test_payment_days_based_on_joining_date@salary.com", "Monthly", "Test Payment Based On Attendence")
+
+		self.assertEqual(new_ss.total_working_days, no_of_days[0])
+		self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
+
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Attendance",
+		"consider_unmarked_attendance_as": "Absent",
+		"include_holidays_in_total_working_days": False
+	})
+	def test_payment_days_for_mid_joinee_excluding_holidays(self):
+		from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+
+		no_of_days = self.get_no_of_days()
+		month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
+
+		new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
+		joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
+		frappe.db.set_value("Employee", new_emp_id, {
+			"date_of_joining": joining_date,
+			"relieving_date": relieving_date,
+			"status": "Left"
+		})
+
+		holidays = 0
+
+		for days in range(date_diff(relieving_date, joining_date) + 1):
+			date = add_days(joining_date, days)
+			if not is_holiday("Salary Slip Test Holiday List", date):
+				mark_attendance(new_emp_id, date, 'Present', ignore_validate=True)
+			else:
+				holidays += 1
+
+		new_ss = make_employee_salary_slip("test_payment_days_based_on_joining_date@salary.com", "Monthly", "Test Payment Based On Attendence")
+
+		self.assertEqual(new_ss.total_working_days, no_of_days[0] - no_of_days[1])
+		self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
+
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Leave"
+	})
 	def test_payment_days_based_on_leave_application(self):
 		no_of_days = self.get_no_of_days()
 
-		# Payroll based on attendance
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
-
 		emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
 		frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
 
@@ -133,8 +200,9 @@
 
 		self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
 
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
-
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Attendance"
+	})
 	def test_payment_days_in_salary_slip_based_on_timesheet(self):
 		from erpnext.hr.doctype.attendance.attendance import mark_attendance
 		from erpnext.projects.doctype.timesheet.test_timesheet import (
@@ -145,9 +213,6 @@
 			make_salary_slip as make_salary_slip_for_timesheet,
 		)
 
-		# Payroll based on attendance
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
-
 		emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company", holiday_list="Salary Slip Test Holiday List")
 		frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
 
@@ -185,17 +250,15 @@
 
 		self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
 
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
-
+	@change_settings("Payroll Settings", {
+		"payroll_based_on": "Attendance"
+	})
 	def test_component_amount_dependent_on_another_payment_days_based_component(self):
 		from erpnext.hr.doctype.attendance.attendance import mark_attendance
 		from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
 			create_salary_structure_assignment,
 		)
 
-		# Payroll based on attendance
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
-
 		salary_structure = make_salary_structure_for_payment_days_based_component_dependency()
 		employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company")
 
@@ -238,11 +301,12 @@
 		expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision)
 
 		self.assertEqual(actual_amount, expected_amount)
-		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
 
+	@change_settings("Payroll Settings", {
+		"include_holidays_in_total_working_days": 1
+	})
 	def test_salary_slip_with_holidays_included(self):
 		no_of_days = self.get_no_of_days()
-		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
 		make_employee("test_salary_slip_with_holidays_included@salary.com")
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
 			{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None)
@@ -256,9 +320,11 @@
 		self.assertEqual(ss.earnings[1].amount, 3000)
 		self.assertEqual(ss.gross_pay, 78000)
 
+	@change_settings("Payroll Settings", {
+		"include_holidays_in_total_working_days": 0
+	})
 	def test_salary_slip_with_holidays_excluded(self):
 		no_of_days = self.get_no_of_days()
-		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
 		make_employee("test_salary_slip_with_holidays_excluded@salary.com")
 		frappe.db.set_value("Employee", frappe.get_value("Employee",
 			{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None)
@@ -273,14 +339,15 @@
 		self.assertEqual(ss.earnings[1].amount, 3000)
 		self.assertEqual(ss.gross_pay, 78000)
 
+	@change_settings("Payroll Settings", {
+		"include_holidays_in_total_working_days": 1
+	})
 	def test_payment_days(self):
 		from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
 			create_salary_structure_assignment,
 		)
 
 		no_of_days = self.get_no_of_days()
-		# Holidays not included in working days
-		frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
 
 		# set joinng date in the same month
 		employee = make_employee("test_payment_days@salary.com")
@@ -338,11 +405,12 @@
 		frappe.set_user("test_employee_salary_slip_read_permission@salary.com")
 		self.assertTrue(salary_slip_test_employee.has_permission("read"))
 
+	@change_settings("Payroll Settings", {
+		"email_salary_slip_to_employee": 1
+	})
 	def test_email_salary_slip(self):
 		frappe.db.sql("delete from `tabEmail Queue`")
 
-		frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 1)
-
 		make_employee("test_email_salary_slip@salary.com")
 		ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
 		ss.company = "_Test Company"
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index f615f05..453d46c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -116,7 +116,7 @@
 
 	currency: function(frm) {
 		let base_currency = frappe.defaults.get_global_default('currency');
-		if (base_currency != frm.doc.currency) {
+		if (frm.doc.currency && (base_currency != frm.doc.currency)) {
 			frappe.call({
 				method: "erpnext.setup.utils.get_exchange_rate",
 				args: {
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
index 1eb3d0d..3ca28c1 100644
--- a/erpnext/projects/report/project_profitability/test_project_profitability.py
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -1,6 +1,5 @@
-import unittest
-
 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
@@ -12,7 +11,7 @@
 from erpnext.projects.report.project_profitability.project_profitability import execute
 
 
-class TestProjectProfitability(unittest.TestCase):
+class TestProjectProfitability(FrappeTestCase):
 	def setUp(self):
 		frappe.db.sql('delete from `tabTimesheet`')
 		emp = make_employee('test_employee_9@salary.com', company='_Test Company')
@@ -67,6 +66,3 @@
 
 		fractional_cost = self.salary_slip.base_gross_pay * utilization
 		self.assertEqual(fractional_cost, row.fractional_cost)
-
-	def tearDown(self):
-		frappe.db.rollback()
diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json
index 78e5651..2c04c6d 100644
--- a/erpnext/regional/india/e_invoice/einv_item_template.json
+++ b/erpnext/regional/india/e_invoice/einv_item_template.json
@@ -23,9 +23,5 @@
     "StateCesAmt": "{item.state_cess_amount}",
     "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
     "OthChrg": "{item.other_charges}",
-    "TotItemVal": "{item.total_value}",
-    "BchDtls": {{
-        "Nm": "{item.batch_no}",
-        "ExpDt": "{item.batch_expiry_date}"
-    }}
+    "TotItemVal": "{item.total_value}"
 }}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index e3f7e90..64c75c4 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -214,8 +214,6 @@
 		item.taxable_value = abs(item.taxable_value)
 		item.discount_amount = 0
 
-		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
-		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
 		item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N'
 		item.serial_no = ""
 
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 0e1a915..34e9a52 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -40,7 +40,6 @@
 
 erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController {
 	onload(doc, dt, dn) {
-		var me = this;
 		super.onload(doc, dt, dn);
 	}
 	party_name() {
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index eb98e6c..f80eaf2 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -562,6 +562,7 @@
 		var me = this;
 		var dialog = new frappe.ui.Dialog({
 			title: __("Select Items"),
+			size: "large",
 			fields: [
 				{
 					"fieldtype": "Check",
@@ -663,7 +664,8 @@
 			} else {
 				let po_items = [];
 				me.frm.doc.items.forEach(d => {
-					let pending_qty = (flt(d.stock_qty) - flt(d.ordered_qty)) / flt(d.conversion_factor);
+					let ordered_qty = me.get_ordered_qty(d, me.frm.doc);
+					let pending_qty = (flt(d.stock_qty) - ordered_qty) / flt(d.conversion_factor);
 					if (pending_qty > 0) {
 						po_items.push({
 							"doctype": "Sales Order Item",
@@ -689,6 +691,24 @@
 		dialog.show();
 	}
 
+	get_ordered_qty(item, so) {
+		let ordered_qty = item.ordered_qty;
+		if (so.packed_items) {
+			// calculate ordered qty based on packed items in case of product bundle
+			let packed_items = so.packed_items.filter(
+				(pi) => pi.parent_detail_docname == item.name
+			);
+			if (packed_items) {
+				ordered_qty = packed_items.reduce(
+					(sum, pi) => sum + flt(pi.ordered_qty),
+					0
+				);
+				ordered_qty = ordered_qty / packed_items.length;
+			}
+		}
+		return ordered_qty;
+	}
+
 	hold_sales_order(){
 		var me = this;
 		var d = new frappe.ui.Dialog({
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 0f5b1e3..abbb3c9 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -877,6 +877,9 @@
 		target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
 		target.project = source_parent.project
 
+	def update_item_for_packed_item(source, target, source_parent):
+		target.qty = flt(source.qty) - flt(source.ordered_qty)
+
 	# po = frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")})
 	doc = get_mapped_doc("Sales Order", source_name, {
 		"Sales Order": {
@@ -920,6 +923,7 @@
 		"Packed Item": {
 			"doctype": "Purchase Order Item",
 			"field_map":  [
+				["name", "sales_order_packed_item"],
 				["parent", "sales_order"],
 				["uom", "uom"],
 				["conversion_factor", "conversion_factor"],
@@ -934,6 +938,7 @@
 				"supplier",
 				"pricing_rules"
 			],
+			"postprocess": update_item_for_packed_item,
 			"condition": lambda doc: doc.parent_item in items_to_map
 		}
 	}, target_doc, set_missing_values)
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index f5a34c0..b528479 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -959,6 +959,42 @@
 		self.assertEqual(purchase_order.items[0].item_code, "_Test Bundle Item 1")
 		self.assertEqual(purchase_order.items[1].item_code, "_Test Bundle Item 2")
 
+	def test_purchase_order_updates_packed_item_ordered_qty(self):
+		"""
+			Tests if the packed item's `ordered_qty` is updated with the quantity of the Purchase Order
+		"""
+		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order
+
+		product_bundle = make_item("_Test Product Bundle", {"is_stock_item": 0})
+		make_item("_Test Bundle Item 1", {"is_stock_item": 1})
+		make_item("_Test Bundle Item 2", {"is_stock_item": 1})
+
+		make_product_bundle("_Test Product Bundle",
+			["_Test Bundle Item 1", "_Test Bundle Item 2"])
+
+		so_items = [
+			{
+				"item_code": product_bundle.item_code,
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier'
+			}
+		]
+
+		so = make_sales_order(item_list=so_items)
+
+		purchase_order = make_purchase_order(so.name, selected_items=so_items)
+		purchase_order.supplier = "_Test Supplier"
+		purchase_order.set_warehouse = "_Test Warehouse - _TC"
+		purchase_order.save()
+		purchase_order.submit()
+
+		so.reload()
+		self.assertEqual(so.packed_items[0].ordered_qty, 2)
+		self.assertEqual(so.packed_items[1].ordered_qty, 2)
+
 	def test_reserved_qty_for_closing_so(self):
 		bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
 			fields=["reserved_qty"])
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 95b1e8b..36ad8fe 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -3,7 +3,6 @@
 
 
 import json
-import os
 
 import frappe
 import frappe.defaults
@@ -422,14 +421,14 @@
 	return " - ".join(parts)
 
 def install_country_fixtures(company, country):
-	path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))
-	if os.path.exists(path.encode("utf-8")):
-		try:
-			module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(country))
-			frappe.get_attr(module_name)(company, False)
-		except Exception as e:
-			frappe.log_error()
-			frappe.throw(_("Failed to setup defaults for country {0}. Please contact support.").format(frappe.bold(country)))
+	try:
+		module_name = f"erpnext.regional.{frappe.scrub(country)}.setup.setup"
+		frappe.get_attr(module_name)(company, False)
+	except ImportError:
+		pass
+	except Exception:
+		frappe.log_error()
+		frappe.throw(_("Failed to setup defaults for country {0}. Please contact support.").format(frappe.bold(country)))
 
 
 def update_company_current_month_sales(company):
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index dbaefc1..6dc4fee 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -11,6 +11,7 @@
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.utils import update_gl_entries_after
 from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item
+from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
 	get_gl_entries,
 	make_purchase_receipt,
@@ -177,6 +178,53 @@
 		self.assertEqual(serial_no.purchase_rate - serial_no_rate, 5.0)
 		self.assertEqual(serial_no.warehouse, "Stores - TCP1")
 
+	def test_serialized_lcv_delivered(self):
+		"""In some cases you'd want to deliver before you can know all the
+		landed costs, this should be allowed for serial nos too.
+
+		Case:
+			- receipt a serial no @ X rate
+			- delivery the serial no @ X rate
+			- add LCV to receipt X + Y
+			- LCV should be successful
+			- delivery should reflect X+Y valuation.
+		"""
+		serial_no = "LCV_TEST_SR_NO"
+		item_code = "_Test Serialized Item"
+		warehouse = "Stores - TCP1"
+
+		pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+				warehouse=warehouse, qty=1, rate=200,
+				item_code=item_code, serial_no=serial_no)
+
+		serial_no_rate = frappe.db.get_value("Serial No", serial_no, "purchase_rate")
+
+		# deliver it before creating LCV
+		dn = create_delivery_note(item_code=item_code,
+				company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
+				serial_no=serial_no, qty=1, rate=500,
+				cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
+
+		charges = 10
+		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
+
+		new_purchase_rate = serial_no_rate + charges
+
+		serial_no = frappe.db.get_value("Serial No", serial_no,
+			["warehouse", "purchase_rate"], as_dict=1)
+
+		self.assertEqual(serial_no.purchase_rate, new_purchase_rate)
+
+		stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+				filters={
+					"voucher_no": dn.name,
+					"voucher_type": dn.doctype,
+					"is_cancelled": 0  # LCV cancels with same name.
+				},
+				fieldname="stock_value_difference")
+
+		# reposting should update the purchase rate in future delivery
+		self.assertEqual(stock_value_difference, -new_purchase_rate)
 
 	def test_landed_cost_voucher_for_odd_numbers (self):
 		pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True)
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 1cda781..866f3ab 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -626,13 +626,13 @@
 		mr.schedule_date = today()
 
 		if not frappe.db.get_value('UOM Conversion Detail',
-			 {'parent': item.item_code, 'uom': 'Kg'}):
-			 item_doc = frappe.get_doc('Item', item.item_code)
-			 item_doc.append('uoms', {
-				 'uom': 'Kg',
-				 'conversion_factor': 5
-			 })
-			 item_doc.save(ignore_permissions=True)
+			{'parent': item.item_code, 'uom': 'Kg'}):
+			item_doc = frappe.get_doc('Item', item.item_code)
+			item_doc.append('uoms', {
+				'uom': 'Kg',
+				'conversion_factor': 5
+			})
+			item_doc.save(ignore_permissions=True)
 
 		item.uom = 'Kg'
 		for item in mr.items:
diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json
index d2d4789..d6e2e9c 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.json
+++ b/erpnext/stock/doctype/packed_item/packed_item.json
@@ -26,6 +26,7 @@
   "section_break_13",
   "actual_qty",
   "projected_qty",
+  "ordered_qty",
   "column_break_16",
   "incoming_rate",
   "page_break",
@@ -224,13 +225,21 @@
    "label": "Rate",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "fieldname": "ordered_qty",
+   "fieldtype": "Float",
+   "label": "Ordered Qty",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-01-28 16:03:30.780111",
+ "modified": "2022-02-22 12:57:45.325488",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Packed Item",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 5484a11..b2eaecb 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -272,9 +272,9 @@
 			and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
 			{warehouse_condition}
 		GROUP BY
-			`warehouse`,
-			`batch_no`,
-			`item_code`
+			sle.`warehouse`,
+			sle.`batch_no`,
+			sle.`item_code`
 		HAVING `qty` > 0
 		ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
 	""".format(warehouse_condition=warehouse_condition), { #nosec
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index a24acb1..fa28f22 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -161,6 +161,15 @@
 				qty=abs(existing_bin_qty)
 			)
 
+		existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
+			"Bin",
+			{
+				"item_code": "_Test Item",
+				"warehouse": "_Test Warehouse - _TC"
+			},
+			["actual_qty", "stock_value"]
+		)
+
 		pr = make_purchase_receipt()
 
 		stock_value_difference = frappe.db.get_value(
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 01d25b2..684a8d4 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -389,10 +389,13 @@
 			)
 
 
-	def assertSLEs(self, doc, expected_sles):
+	def assertSLEs(self, doc, expected_sles, sle_filters=None):
 		""" Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
-		sles = frappe.get_all("Stock Ledger Entry", fields=["*"],
-				filters={"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled":0},
+
+		filters = {"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled": 0}
+		if sle_filters:
+			filters.update(sle_filters)
+		sles = frappe.get_all("Stock Ledger Entry", fields=["*"], filters=filters,
 			order_by="timestamp(posting_date, posting_time), creation")
 
 		for exp_sle, act_sle in zip(expected_sles, sles):
@@ -665,6 +668,78 @@
 			{"actual_qty": -10, "stock_value_difference": -10*40, "stock_queue": []},
 		]))
 
+	def test_fifo_dependent_consumption(self):
+		item = make_item("_TestFifoTransferRates")
+		source = "_Test Warehouse - _TC"
+		target = "Stores - _TC"
+
+		rates = [10 * i for i in range(1, 20)]
+
+		receipt = make_stock_entry(item_code=item.name, target=source, qty=10, do_not_save=True, rate=10)
+		for rate in rates[1:]:
+			row = frappe.copy_doc(receipt.items[0], ignore_no_copy=False)
+			row.basic_rate = rate
+			receipt.append("items", row)
+
+		receipt.save()
+		receipt.submit()
+
+		expected_queues = []
+		for idx, rate in enumerate(rates, start=1):
+			expected_queues.append(
+				{"stock_queue": [[10, 10 * i] for i in range(1, idx + 1)]}
+			)
+		self.assertSLEs(receipt, expected_queues)
+
+		transfer = make_stock_entry(item_code=item.name, source=source, target=target, qty=10, do_not_save=True, rate=10)
+		for rate in rates[1:]:
+			row = frappe.copy_doc(transfer.items[0], ignore_no_copy=False)
+			transfer.append("items", row)
+
+		transfer.save()
+		transfer.submit()
+
+		# same exact queue should be transferred
+		self.assertSLEs(transfer, expected_queues, sle_filters={"warehouse": target})
+
+	def test_fifo_multi_item_repack_consumption(self):
+		rm = make_item("_TestFifoRepackRM")
+		packed = make_item("_TestFifoRepackFinished")
+		warehouse = "_Test Warehouse - _TC"
+
+		rates = [10 * i for i in range(1, 5)]
+
+		receipt = make_stock_entry(item_code=rm.name, target=warehouse, qty=10, do_not_save=True, rate=10)
+		for rate in rates[1:]:
+			row = frappe.copy_doc(receipt.items[0], ignore_no_copy=False)
+			row.basic_rate = rate
+			receipt.append("items", row)
+
+		receipt.save()
+		receipt.submit()
+
+		repack = make_stock_entry(item_code=rm.name, source=warehouse, qty=10,
+				do_not_save=True, rate=10, purpose="Repack")
+		for rate in rates[1:]:
+			row = frappe.copy_doc(repack.items[0], ignore_no_copy=False)
+			repack.append("items", row)
+
+		repack.append("items", {
+			"item_code": packed.name,
+			"t_warehouse": warehouse,
+			"qty": 1,
+			"transfer_qty": 1,
+		})
+
+		repack.save()
+		repack.submit()
+
+		# same exact queue should be transferred
+		self.assertSLEs(repack, [
+			{"incoming_rate": sum(rates) * 10}
+		], sle_filters={"item_code": packed.name})
+
+
 def create_repack_entry(**args):
 	args = frappe._dict(args)
 	repack = frappe.new_doc("Stock Entry")
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 05076b5..c695d54 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -244,7 +244,7 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2021-12-03 04:40:06.414630",
+ "modified": "2022-03-01 02:37:48.034944",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Warehouse",
@@ -301,5 +301,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "title_field": "warehouse_name"
+ "states": [],
+ "title_field": "warehouse_name",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index 2630805..ca963b7 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
 import frappe
diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
index 7826d34..1ba2482 100644
--- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
+++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
@@ -21,6 +21,7 @@
 	"stock_value",
 	"stock_value_difference",
 	"valuation_rate",
+	"voucher_detail_no",
 )
 
 
@@ -66,7 +67,9 @@
 		balance_qty += sle.actual_qty
 		balance_stock_value += sle.stock_value_difference
 		if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
-			balance_qty = sle.qty_after_transaction
+			balance_qty = frappe.db.get_value("Stock Reconciliation Item", sle.voucher_detail_no, "qty")
+			if balance_qty is None:
+				balance_qty = sle.qty_after_transaction
 
 		sle.fifo_queue_qty = fifo_qty
 		sle.fifo_stock_value = fifo_value
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 1b90086..ba1081f 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -28,6 +28,16 @@
 
 
 def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
+	""" Create SL entries from SL entry dicts
+
+		args:
+			- allow_negative_stock: disable negative stock valiations if true
+			- via_landed_cost_voucher: landed cost voucher cancels and reposts
+			entries of purchase document. This flag is used to identify if
+			cancellation and repost is happening via landed cost voucher, in
+			such cases certain validations need to be ignored (like negative
+					stock)
+	"""
 	from erpnext.controllers.stock_controller import future_sle_exists
 	if sl_entries:
 		cancel = sl_entries[0].get("is_cancelled")
@@ -39,7 +49,7 @@
 		future_sle_exists(args, sl_entries)
 
 		for sle in sl_entries:
-			if sle.serial_no:
+			if sle.serial_no and not via_landed_cost_voucher:
 				validate_serial_no(sle)
 
 			if cancel:
@@ -819,7 +829,7 @@
 		if msg_list:
 			message = "\n\n".join(msg_list)
 			if self.verbose:
-				frappe.throw(message, NegativeStockError, title='Insufficient Stock')
+				frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
 			else:
 				raise NegativeStockError(message)
 
@@ -1147,7 +1157,7 @@
 			neg_sle[0]["posting_date"], neg_sle[0]["posting_time"],
 			frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"]))
 
-		frappe.throw(message, NegativeStockError, title='Insufficient Stock')
+		frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
 
 
 	if not args.batch_no:
@@ -1161,7 +1171,7 @@
 			frappe.get_desk_link('Warehouse', args.warehouse),
 			neg_batch_sle[0]["posting_date"], neg_batch_sle[0]["posting_time"],
 			frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]))
-		frappe.throw(message, NegativeStockError, title="Insufficient Stock for Batch")
+		frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
 
 
 def get_future_sle_with_negative_qty(args):
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index 82b2716..faf5e92 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1,27 +1 @@
-{% set domains = frappe.get_doc("Domain Settings").active_domains %}
-{% set links = {
-	'Manufacturing': '/manufacturing',
-	'Services': '/services',
-	'Retail': '/retail',
-	'Distribution': '/distribution',
-	'Non Profit': '/non-profit',
-	'Education': '/education',
-	'Healthcare': '/healthcare',
-	'Agriculture': '/agriculture',
-	'Hospitality': ''
-} %}
-
-{% set link = '' %}
-{% set label = '' %}
-{% if domains %}
-	{% set label = domains[0].domain %}
-	{% set link = links[label] %}
-{% endif %}
-
-{% if label == "Services" %}
-	{% set label = "Service" %}
-{% endif %}
-
-
-
-<a href="https://erpnext.com{{ link }}?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + label + ' Companies') if domains else '' }}</a>
+<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
diff --git a/erpnext/tests/test_point_of_sale.py b/erpnext/tests/test_point_of_sale.py
index 3299c88..38f2c16 100644
--- a/erpnext/tests/test_point_of_sale.py
+++ b/erpnext/tests/test_point_of_sale.py
@@ -25,7 +25,7 @@
 		Test Stock and Service Item Search.
 		"""
 
-		pos_profile = make_pos_profile()
+		pos_profile = make_pos_profile(name="Test POS Profile for Search")
 		item1 = make_item("Test Search Stock Item", {"is_stock_item": 1})
 		make_stock_entry(
 			item_code="Test Search Stock Item",