Merge branch 'develop' into de-translate-employee
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index f2a696d..a0c0ecc 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2648,6 +2648,7 @@
 		# reset
 		einvoice_settings = frappe.get_doc("E Invoice Settings")
 		einvoice_settings.enable = 0
+		einvoice_settings.save()
 		frappe.flags.country = country
 
 	def test_einvoice_json(self):
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 5bfca96..4d20129 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -163,17 +163,15 @@
 def get_tax_template(posting_date, args):
 	"""Get matching tax rule"""
 	args = frappe._dict(args)
-	from_date = to_date = posting_date
-	if not posting_date:
-		from_date = "1900-01-01"
-		to_date = "4000-01-01"
+	conditions = []
 
-	conditions = [
-		"""(from_date is null or from_date <= '{0}')
-		and (to_date is null or to_date >= '{1}')""".format(
-			from_date, to_date
+	if posting_date:
+		conditions.append(
+			f"""(from_date is null or from_date <= '{posting_date}')
+			and (to_date is null or to_date >= '{posting_date}')"""
 		)
-	]
+	else:
+		conditions.append("(from_date is null) and (to_date is null)")
 
 	conditions.append(
 		"ifnull(tax_category, '') = {0}".format(frappe.db.escape(cstr(args.get("tax_category"))))
diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py
index 1bda0d8..9c0aba3 100644
--- a/erpnext/accounts/report/pos_register/pos_register.py
+++ b/erpnext/accounts/report/pos_register/pos_register.py
@@ -62,7 +62,7 @@
 		"""
 		SELECT
 			p.posting_date, p.name as pos_invoice, p.pos_profile,
-			p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount,
+			p.owner, p.base_grand_total as grand_total, p.base_paid_amount - p.change_amount  as paid_amount,
 			p.customer, p.is_return {select_mop_field}
 		FROM
 			`tabPOS Invoice` p {from_sales_invoice_payment}
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 19b4d68..b590177 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -9,7 +9,7 @@
 from frappe.email.inbox import link_communication_to_document
 from frappe.model.mapper import get_mapped_doc
 from frappe.query_builder import DocType
-from frappe.utils import cint, cstr, flt, get_fullname
+from frappe.utils import cint, flt, get_fullname
 
 from erpnext.crm.utils import add_link_in_communication, copy_comments
 from erpnext.setup.utils import get_exchange_rate
@@ -215,20 +215,20 @@
 
 			if self.party_name and self.opportunity_from == "Customer":
 				if self.contact_person:
-					opts.description = "Contact " + cstr(self.contact_person)
+					opts.description = f"Contact {self.contact_person}"
 				else:
-					opts.description = "Contact customer " + cstr(self.party_name)
+					opts.description = f"Contact customer {self.party_name}"
 			elif self.party_name and self.opportunity_from == "Lead":
 				if self.contact_display:
-					opts.description = "Contact " + cstr(self.contact_display)
+					opts.description = f"Contact {self.contact_display}"
 				else:
-					opts.description = "Contact lead " + cstr(self.party_name)
+					opts.description = f"Contact lead {self.party_name}"
 
 			opts.subject = opts.description
-			opts.description += ". By : " + cstr(self.contact_by)
+			opts.description += f". By : {self.contact_by}"
 
 			if self.to_discuss:
-				opts.description += " To Discuss : " + cstr(self.to_discuss)
+				opts.description += f" To Discuss : {frappe.render_template(self.to_discuss, {'doc': self})}"
 
 			super(Opportunity, self).add_calendar_event(opts, force)
 
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 481c7f1..4a18e94 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -4,7 +4,7 @@
 import unittest
 
 import frappe
-from frappe.utils import now_datetime, random_string, today
+from frappe.utils import add_days, now_datetime, random_string, today
 
 from erpnext.crm.doctype.lead.lead import make_customer
 from erpnext.crm.doctype.lead.test_lead import make_lead
@@ -97,6 +97,22 @@
 		self.assertEqual(quotation_comment_count, 4)
 		self.assertEqual(quotation_communication_count, 4)
 
+	def test_render_template_for_to_discuss(self):
+		doc = make_opportunity(with_items=0, opportunity_from="Lead")
+		doc.contact_by = "test@example.com"
+		doc.contact_date = add_days(today(), days=2)
+		doc.to_discuss = "{{ doc.name }} test data"
+		doc.save()
+
+		event = frappe.get_all(
+			"Event Participants",
+			fields=["parent"],
+			filters={"reference_doctype": doc.doctype, "reference_docname": doc.name},
+		)
+
+		event_description = frappe.db.get_value("Event", event[0].parent, "description")
+		self.assertTrue(doc.name in event_description)
+
 
 def make_opportunity_from_lead():
 	new_lead_email_id = "new{}@example.com".format(random_string(5))
diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
index 437ebea..30d9ffc 100644
--- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
+++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
@@ -139,7 +139,7 @@
 		tax_rule_master = set_taxes(
 			quotation.party_name,
 			"Customer",
-			quotation.transaction_date,
+			None,
 			quotation.company,
 			customer_group=None,
 			supplier_group=None,
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 066249b..d1b5113 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -12,7 +12,7 @@
 app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
 
 
-develop_version = "13.x.x-develop"
+develop_version = "14.x.x-develop"
 
 app_include_js = "erpnext.bundle.js"
 app_include_css = "erpnext.bundle.css"
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index aef4412..9742387 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -34,15 +34,6 @@
 				});
 			}
 		}
-
-		// make new leaves allocated field read only if allocation is created via leave policy assignment
-		// and leave type is earned leave, since these leaves would be allocated via the scheduler
-		if (frm.doc.leave_policy_assignment) {
-			frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => {
-				if (r && cint(r.is_earned_leave))
-					frm.set_df_property("new_leaves_allocated", "read_only", 1);
-			});
-		}
 	},
 
 	expire_allocation: function(frm) {
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 27479a5..8fae2a9 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -254,7 +254,18 @@
 		# Adding a day to include To Date in the difference
 		date_difference = date_diff(self.to_date, self.from_date) + 1
 		if date_difference < self.total_leaves_allocated:
-			frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
+			if frappe.db.get_value("Leave Type", self.leave_type, "allow_over_allocation"):
+				frappe.msgprint(
+					_("<b>Total Leaves Allocated</b> are more than the number of days in the allocation period"),
+					indicator="orange",
+					alert=True,
+				)
+			else:
+				frappe.throw(
+					_("<b>Total Leaves Allocated</b> are more than the number of days in the allocation period"),
+					exc=OverAllocationError,
+					title=_("Over Allocation"),
+				)
 
 	def create_leave_ledger_entry(self, submit=True):
 		if self.unused_leaves:
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index a1d39d4..b4a42d3 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -69,22 +69,44 @@
 		self.assertRaises(frappe.ValidationError, doc.save)
 
 	def test_validation_for_over_allocation(self):
+		leave_type = create_leave_type(leave_type_name="Test Over Allocation", is_carry_forward=1)
+		leave_type.save()
+
 		doc = frappe.get_doc(
 			{
 				"doctype": "Leave Allocation",
 				"__islocal": 1,
 				"employee": self.employee.name,
 				"employee_name": self.employee.employee_name,
-				"leave_type": "_Test Leave Type",
+				"leave_type": leave_type.name,
 				"from_date": getdate("2015-09-1"),
 				"to_date": getdate("2015-09-30"),
 				"new_leaves_allocated": 35,
+				"carry_forward": 1,
 			}
 		)
 
 		# allocated leave more than period
 		self.assertRaises(OverAllocationError, doc.save)
 
+		leave_type.allow_over_allocation = 1
+		leave_type.save()
+
+		# allows creating a leave allocation with more leave days than period days
+		doc = frappe.get_doc(
+			{
+				"doctype": "Leave Allocation",
+				"__islocal": 1,
+				"employee": self.employee.name,
+				"employee_name": self.employee.employee_name,
+				"leave_type": leave_type.name,
+				"from_date": getdate("2015-09-1"),
+				"to_date": getdate("2015-09-30"),
+				"new_leaves_allocated": 35,
+				"carry_forward": 1,
+			}
+		).insert()
+
 	def test_validation_for_over_allocation_post_submission(self):
 		allocation = frappe.get_doc(
 			{
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 4c39e15..7506c61 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -745,7 +745,7 @@
 
 		i = 0
 		while i < 14:
-			allocate_earned_leaves(ignore_duplicates=True)
+			allocate_earned_leaves()
 			i += 1
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
 
@@ -753,7 +753,7 @@
 		frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
 		i = 0
 		while i < 6:
-			allocate_earned_leaves(ignore_duplicates=True)
+			allocate_earned_leaves()
 			i += 1
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
 
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 9780828..031ed0e 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -4,6 +4,7 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, add_months, get_first_day, get_last_day, getdate
 
 from erpnext.hr.doctype.leave_application.test_leave_application import (
@@ -18,7 +19,7 @@
 test_dependencies = ["Employee"]
 
 
-class TestLeavePolicyAssignment(unittest.TestCase):
+class TestLeavePolicyAssignment(FrappeTestCase):
 	def setUp(self):
 		for doctype in [
 			"Leave Period",
@@ -39,6 +40,9 @@
 		leave_policy = create_leave_policy()
 		leave_policy.submit()
 
+		self.employee.date_of_joining = get_first_day(leave_period.from_date)
+		self.employee.save()
+
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
@@ -188,19 +192,6 @@
 		)
 		self.assertEqual(leaves_allocated, 3)
 
-		# if the daily job is not completed yet, there is another check present
-		# to ensure leave is not already allocated to avoid duplication
-		from erpnext.hr.utils import allocate_earned_leaves
-
-		allocate_earned_leaves()
-
-		leaves_allocated = frappe.db.get_value(
-			"Leave Allocation",
-			{"leave_policy_assignment": leave_policy_assignments[0]},
-			"total_leaves_allocated",
-		)
-		self.assertEqual(leaves_allocated, 3)
-
 	def test_earned_leave_alloc_for_passed_months_with_cf_leaves_based_on_leave_period(self):
 		from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
 
@@ -242,20 +233,6 @@
 		self.assertEqual(details.unused_leaves, 5)
 		self.assertEqual(details.total_leaves_allocated, 7)
 
-		# if the daily job is not completed yet, there is another check present
-		# to ensure leave is not already allocated to avoid duplication
-		from erpnext.hr.utils import is_earned_leave_already_allocated
-
-		frappe.flags.current_date = get_last_day(getdate())
-
-		allocation = frappe.get_doc("Leave Allocation", details.name)
-		# 1 leave is still pending to be allocated, irrespective of carry forwarded leaves
-		self.assertFalse(
-			is_earned_leave_already_allocated(
-				allocation, leave_policy.leave_policy_details[0].annual_allocation
-			)
-		)
-
 	def test_earned_leave_alloc_for_passed_months_based_on_joining_date(self):
 		# tests leave alloc for earned leaves for assignment based on joining date in policy assignment
 		leave_type = create_earned_leave_type("Test Earned Leave")
@@ -288,19 +265,6 @@
 		self.assertEqual(effective_from, self.employee.date_of_joining)
 		self.assertEqual(leaves_allocated, 3)
 
-		# to ensure leave is not already allocated to avoid duplication
-		from erpnext.hr.utils import allocate_earned_leaves
-
-		frappe.flags.current_date = get_last_day(getdate())
-		allocate_earned_leaves()
-
-		leaves_allocated = frappe.db.get_value(
-			"Leave Allocation",
-			{"leave_policy_assignment": leave_policy_assignments[0]},
-			"total_leaves_allocated",
-		)
-		self.assertEqual(leaves_allocated, 3)
-
 	def test_grant_leaves_on_doj_for_earned_leaves_based_on_leave_period(self):
 		# tests leave alloc based on leave period for earned leaves with "based on doj" configuration in leave type
 		leave_period, leave_policy = setup_leave_period_and_policy(
@@ -330,20 +294,6 @@
 		)
 		self.assertEqual(leaves_allocated, 3)
 
-		# if the daily job is not completed yet, there is another check present
-		# to ensure leave is not already allocated to avoid duplication
-		from erpnext.hr.utils import allocate_earned_leaves
-
-		frappe.flags.current_date = get_first_day(getdate())
-		allocate_earned_leaves()
-
-		leaves_allocated = frappe.db.get_value(
-			"Leave Allocation",
-			{"leave_policy_assignment": leave_policy_assignments[0]},
-			"total_leaves_allocated",
-		)
-		self.assertEqual(leaves_allocated, 3)
-
 	def test_grant_leaves_on_doj_for_earned_leaves_based_on_joining_date(self):
 		# tests leave alloc based on joining date for earned leaves with "based on doj" configuration in leave type
 		leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj=True)
@@ -377,21 +327,7 @@
 		self.assertEqual(effective_from, self.employee.date_of_joining)
 		self.assertEqual(leaves_allocated, 3)
 
-		# to ensure leave is not already allocated to avoid duplication
-		from erpnext.hr.utils import allocate_earned_leaves
-
-		frappe.flags.current_date = get_first_day(getdate())
-		allocate_earned_leaves()
-
-		leaves_allocated = frappe.db.get_value(
-			"Leave Allocation",
-			{"leave_policy_assignment": leave_policy_assignments[0]},
-			"total_leaves_allocated",
-		)
-		self.assertEqual(leaves_allocated, 3)
-
 	def tearDown(self):
-		frappe.db.rollback()
 		frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
 		frappe.flags.current_date = None
 
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index 06ca4cd..d40ff09 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -19,6 +19,7 @@
   "fraction_of_daily_salary_per_leave",
   "is_optional_leave",
   "allow_negative",
+  "allow_over_allocation",
   "include_holiday",
   "is_compensatory",
   "carry_forward_section",
@@ -211,15 +212,23 @@
    "fieldtype": "Float",
    "label": "Fraction of Daily Salary per Leave",
    "mandatory_depends_on": "eval:doc.is_ppl == 1"
+  },
+  {
+   "default": "0",
+   "description": "Allows allocating more leaves than the number of days in the allocation period.",
+   "fieldname": "allow_over_allocation",
+   "fieldtype": "Check",
+   "label": "Allow Over Allocation"
   }
  ],
  "icon": "fa fa-flag",
  "idx": 1,
  "links": [],
- "modified": "2021-10-02 11:59:40.503359",
+ "modified": "2022-05-09 05:01:38.957545",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Type",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
@@ -251,5 +260,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 4e30b0f..269e4aa 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -269,7 +269,7 @@
 		create_leave_encashment(leave_allocation=leave_allocation)
 
 
-def allocate_earned_leaves(ignore_duplicates=False):
+def allocate_earned_leaves():
 	"""Allocate earned leaves to Employees"""
 	e_leave_types = get_earned_leaves()
 	today = getdate()
@@ -305,14 +305,10 @@
 			if check_effective_date(
 				from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining
 			):
-				update_previous_leave_allocation(
-					allocation, annual_allocation, e_leave_type, ignore_duplicates
-				)
+				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
 
 
-def update_previous_leave_allocation(
-	allocation, annual_allocation, e_leave_type, ignore_duplicates=False
-):
+def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
 	earned_leaves = get_monthly_earned_leave(
 		annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
 	)
@@ -326,20 +322,19 @@
 	if new_allocation != allocation.total_leaves_allocated:
 		today_date = today()
 
-		if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
-			allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
-			create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+		allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+		create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
 
-			if e_leave_type.based_on_date_of_joining:
-				text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
-					frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
-				)
-			else:
-				text = _("allocated {0} leave(s) via scheduler on {1}").format(
-					frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
-				)
+		if e_leave_type.based_on_date_of_joining:
+			text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
+				frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
+			)
+		else:
+			text = _("allocated {0} leave(s) via scheduler on {1}").format(
+				frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
+			)
 
-			allocation.add_comment(comment_type="Info", text=text)
+		allocation.add_comment(comment_type="Info", text=text)
 
 
 def get_monthly_earned_leave(annual_leaves, frequency, rounding):
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index 17b018c..ea56d07 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -99,8 +99,21 @@
 									...data
 								},
 								freeze: true,
-								callback: () => frm.reload_doc() || d.hide(),
-								error: () => d.hide()
+								callback: () => {
+									frappe.show_alert({
+										message: __('E-Way Bill Generated successfully'),
+										indicator: 'green'
+									}, 7);
+									frm.reload_doc();
+									d.hide();
+								},
+								error: () => {
+									frappe.show_alert({
+										message: __('E-Way Bill was not Generated'),
+										indicator: 'red'
+									}, 7);
+									d.hide();
+								}
 							});
 						},
 						primary_action_label: __('Submit')
@@ -136,29 +149,83 @@
 			}
 
 			if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
 				const action = () => {
-					let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
-					message += '<br><br>';
-					message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
+					const d = new frappe.ui.Dialog({
+						title: __('Cancel E-Way Bill'),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									eway_bill: ewaybill,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => {
+									frappe.show_alert({
+										message: __('E-Way Bill Cancelled successfully'),
+										indicator: 'green'
+									}, 7);
+									frm.reload_doc();
+									d.hide();
+								},
+								error: () => {
+									frappe.show_alert({
+										message: __('E-Way Bill was not Cancelled'),
+										indicator: 'red'
+									}, 7);
+									d.hide();
+								}
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel E-Way Bill"), action);
+			}
 
+			if (irn && !irn_cancelled) {
+				const action = () => {
 					const dialog = frappe.msgprint({
-						title: __('Update E-Way Bill Cancelled Status?'),
-						message: message,
-						indicator: 'orange',
+						title: __("Generate QRCode"),
+						message: __("Generate and attach QR Code using IRN?"),
 						primary_action: {
 							action: function() {
 								frappe.call({
-									method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+									method: 'erpnext.regional.india.e_invoice.utils.generate_qrcode',
 									args: { doctype, docname: name },
 									freeze: true,
-									callback: () => frm.reload_doc() || dialog.hide()
+									callback: () => frm.reload_doc() || dialog.hide(),
+									error: () => dialog.hide()
 								});
 							}
 						},
 						primary_action_label: __('Yes')
 					});
+					dialog.show();
 				};
-				add_custom_button(__("Cancel E-Way Bill"), action);
+				add_custom_button(__("Generate QRCode"), action);
 			}
 		}
 	});
@@ -167,85 +234,100 @@
 const get_ewaybill_fields = (frm) => {
 	return [
 		{
-			'fieldname': 'transporter',
-			'label': 'Transporter',
-			'fieldtype': 'Link',
-			'options': 'Supplier',
-			'default': frm.doc.transporter
+			fieldname: "eway_part_a_section_break",
+			fieldtype: "Section Break",
+			label: "Part A",
 		},
 		{
-			'fieldname': 'gst_transporter_id',
-			'label': 'GST Transporter ID',
-			'fieldtype': 'Data',
-			'default': frm.doc.gst_transporter_id
+			fieldname: "transporter",
+			label: "Transporter",
+			fieldtype: "Link",
+			options: "Supplier",
+			default: frm.doc.transporter,
 		},
 		{
-			'fieldname': 'driver',
-			'label': 'Driver',
-			'fieldtype': 'Link',
-			'options': 'Driver',
-			'default': frm.doc.driver
+			fieldname: "transporter_name",
+			label: "Transporter Name",
+			fieldtype: "Data",
+			read_only: 1,
+			default: frm.doc.transporter_name,
+			depends_on: "transporter",
 		},
 		{
-			'fieldname': 'lr_no',
-			'label': 'Transport Receipt No',
-			'fieldtype': 'Data',
-			'default': frm.doc.lr_no
+			fieldname: "part_a_column_break",
+			fieldtype: "Column Break",
 		},
 		{
-			'fieldname': 'vehicle_no',
-			'label': 'Vehicle No',
-			'fieldtype': 'Data',
-			'default': frm.doc.vehicle_no
+			fieldname: "gst_transporter_id",
+			label: "GST Transporter ID",
+			fieldtype: "Data",
+			default: frm.doc.gst_transporter_id,
 		},
 		{
-			'fieldname': 'distance',
-			'label': 'Distance (in km)',
-			'fieldtype': 'Float',
-			'default': frm.doc.distance
+			fieldname: "distance",
+			label: "Distance (in km)",
+			fieldtype: "Float",
+			default: frm.doc.distance,
+			description: 'Set as zero to auto calculate distance using pin codes',
 		},
 		{
-			'fieldname': 'transporter_col_break',
-			'fieldtype': 'Column Break',
+			fieldname: "eway_part_b_section_break",
+			fieldtype: "Section Break",
+			label: "Part B",
 		},
 		{
-			'fieldname': 'transporter_name',
-			'label': 'Transporter Name',
-			'fieldtype': 'Data',
-			'read_only': 1,
-			'default': frm.doc.transporter_name,
-			'depends_on': 'transporter'
+			fieldname: "mode_of_transport",
+			label: "Mode of Transport",
+			fieldtype: "Select",
+			options: `\nRoad\nAir\nRail\nShip`,
+			default: frm.doc.mode_of_transport,
 		},
 		{
-			'fieldname': 'mode_of_transport',
-			'label': 'Mode of Transport',
-			'fieldtype': 'Select',
-			'options': `\nRoad\nAir\nRail\nShip`,
-			'default': frm.doc.mode_of_transport
+			fieldname: "gst_vehicle_type",
+			label: "GST Vehicle Type",
+			fieldtype: "Select",
+			options: `Regular\nOver Dimensional Cargo (ODC)`,
+			depends_on: 'eval:(doc.mode_of_transport === "Road")',
+			default: frm.doc.gst_vehicle_type,
 		},
 		{
-			'fieldname': 'driver_name',
-			'label': 'Driver Name',
-			'fieldtype': 'Data',
-			'fetch_from': 'driver.full_name',
-			'read_only': 1,
-			'default': frm.doc.driver_name,
-			'depends_on': 'driver'
+			fieldname: "vehicle_no",
+			label: "Vehicle No",
+			fieldtype: "Data",
+			default: frm.doc.vehicle_no,
 		},
 		{
-			'fieldname': 'lr_date',
-			'label': 'Transport Receipt Date',
-			'fieldtype': 'Date',
-			'default': frm.doc.lr_date
+			fieldname: "part_b_column_break",
+			fieldtype: "Column Break",
 		},
 		{
-			'fieldname': 'gst_vehicle_type',
-			'label': 'GST Vehicle Type',
-			'fieldtype': 'Select',
-			'options': `Regular\nOver Dimensional Cargo (ODC)`,
-			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
-			'default': frm.doc.gst_vehicle_type
-		}
+			fieldname: "lr_date",
+			label: "Transport Receipt Date",
+			fieldtype: "Date",
+			default: frm.doc.lr_date,
+		},
+		{
+			fieldname: "lr_no",
+			label: "Transport Receipt No",
+			fieldtype: "Data",
+			default: frm.doc.lr_no,
+		},
+		{
+			fieldname: "driver",
+			label: "Driver",
+			fieldtype: "Link",
+			options: "Driver",
+			default: frm.doc.driver,
+		},
+		{
+			fieldname: "driver_name",
+			label: "Driver Name",
+			fieldtype: "Data",
+			fetch_from: "driver.full_name",
+			read_only: 1,
+			default: frm.doc.driver_name,
+			depends_on: "driver",
+		},
 	];
 };
 
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index cfe9cee..ed1002a 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -167,7 +167,12 @@
 			title=_("Not Allowed"),
 		)
 
-	invoice_type = "CRN" if invoice.is_return else "INV"
+	if invoice.is_return:
+		invoice_type = "CRN"
+	elif invoice.is_debit_note:
+		invoice_type = "DBN"
+	else:
+		invoice_type = "INV"
 
 	invoice_name = invoice.name
 	invoice_date = format_date(invoice.posting_date, "dd/mm/yyyy")
@@ -792,8 +797,9 @@
 		self.irn_details_url = self.base_url + "/enriched/ei/api/invoice/irn"
 		self.generate_irn_url = self.base_url + "/enriched/ei/api/invoice"
 		self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin"
-		self.cancel_ewaybill_url = self.base_url + "/enriched/ewb/ewayapi?action=CANEWB"
+		self.cancel_ewaybill_url = self.base_url + "/enriched/ei/api/ewayapi"
 		self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill"
+		self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image"
 
 	def set_invoice(self):
 		self.invoice = None
@@ -857,8 +863,8 @@
 		return res
 
 	def auto_refresh_token(self):
-		self.fetch_auth_token()
 		self.token_auto_refreshed = True
+		self.fetch_auth_token()
 
 	def log_request(self, url, headers, data, res):
 		headers.update({"password": self.credentials.password})
@@ -998,6 +1004,37 @@
 
 		return failed
 
+	def fetch_and_attach_qrcode_from_irn(self):
+		qrcode = self.get_qrcode_from_irn(self.invoice.irn)
+		if qrcode:
+			qrcode_file = self.create_qr_code_file(qrcode)
+			frappe.db.set_value("Sales Invoice", self.invoice.name, "qrcode_image", qrcode_file.file_url)
+			frappe.msgprint(_("QR Code attached to the invoice"), alert=True)
+		else:
+			frappe.msgprint(_("QR Code not found for the IRN"), alert=True)
+
+	def get_qrcode_from_irn(self, irn):
+		import requests
+
+		headers = self.get_headers()
+		headers.update({"width": "215", "height": "215", "imgtype": "jpg", "irn": irn})
+
+		try:
+			# using requests.get instead of make_request to avoid parsing the response
+			res = requests.get(self.get_qrcode_url, headers=headers)
+			self.log_request(self.get_qrcode_url, headers, None, None)
+			if res.status_code == 200:
+				return res.content
+			else:
+				raise RequestFailed(str(res.content, "utf-8"))
+
+		except RequestFailed as e:
+			self.raise_error(errors=str(e))
+
+		except Exception:
+			log_error()
+			self.raise_error()
+
 	def get_irn_details(self, irn):
 		headers = self.get_headers()
 
@@ -1113,6 +1150,19 @@
 				self.invoice.eway_bill_validity = res.get("result").get("EwbValidTill")
 				self.invoice.eway_bill_cancelled = 0
 				self.invoice.update(args)
+				if res.get("info"):
+					info = res.get("info")
+					# when we have more features (responses) in eway bill, we can add them using below forloop.
+					for msg in info:
+						if msg.get("InfCd") == "EWBPPD":
+							pin_to_pin_distance = int(re.search(r"\d+", msg.get("Desc")).group())
+							frappe.msgprint(
+								_("Auto Calculated Distance is {} KM.").format(str(pin_to_pin_distance)),
+								title="Notification",
+								indicator="green",
+								alert=True,
+							)
+							self.invoice.distance = flt(pin_to_pin_distance)
 				self.invoice.flags.updater_reference = {
 					"doctype": self.invoice.doctype,
 					"docname": self.invoice.name,
@@ -1135,7 +1185,6 @@
 		headers = self.get_headers()
 		data = json.dumps({"ewbNo": eway_bill, "cancelRsnCode": reason, "cancelRmrk": remark}, indent=4)
 		headers["username"] = headers["user_name"]
-		del headers["user_name"]
 		try:
 			res = self.make_request("post", self.cancel_ewaybill_url, headers, data)
 			if res.get("success"):
@@ -1186,8 +1235,6 @@
 		return errors
 
 	def raise_error(self, raise_exception=False, errors=None):
-		if errors is None:
-			errors = []
 		title = _("E Invoice Request Failed")
 		if errors:
 			frappe.throw(errors, title=title, as_list=1)
@@ -1228,13 +1275,18 @@
 
 	def attach_qrcode_image(self):
 		qrcode = self.invoice.signed_qr_code
-		doctype = self.invoice.doctype
-		docname = self.invoice.name
-		filename = "QRCode_{}.png".format(docname).replace(os.path.sep, "__")
 
 		qr_image = io.BytesIO()
 		url = qrcreate(qrcode, error="L")
 		url.png(qr_image, scale=2, quiet_zone=1)
+		qrcode_file = self.create_qr_code_file(qr_image.getvalue())
+		self.invoice.qrcode_image = qrcode_file.file_url
+
+	def create_qr_code_file(self, qr_image):
+		doctype = self.invoice.doctype
+		docname = self.invoice.name
+		filename = "QRCode_{}.png".format(docname).replace(os.path.sep, "__")
+
 		_file = frappe.get_doc(
 			{
 				"doctype": "File",
@@ -1243,12 +1295,12 @@
 				"attached_to_name": docname,
 				"attached_to_field": "qrcode_image",
 				"is_private": 0,
-				"content": qr_image.getvalue(),
+				"content": qr_image,
 			}
 		)
 		_file.save()
 		frappe.db.commit()
-		self.invoice.qrcode_image = _file.file_url
+		return _file
 
 	def update_invoice(self):
 		self.invoice.flags.ignore_validate_update_after_submit = True
@@ -1294,19 +1346,21 @@
 
 
 @frappe.whitelist()
+def generate_qrcode(doctype, docname):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.fetch_and_attach_qrcode_from_irn()
+
+
+@frappe.whitelist()
 def generate_eway_bill(doctype, docname, **kwargs):
 	gsp_connector = GSPConnector(doctype, docname)
 	gsp_connector.generate_eway_bill(**kwargs)
 
 
 @frappe.whitelist()
-def cancel_eway_bill(doctype, docname):
-	# TODO: uncomment when eway_bill api from Adequare is enabled
-	# gsp_connector = GSPConnector(doctype, docname)
-	# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
-
-	frappe.db.set_value(doctype, docname, "ewaybill", "")
-	frappe.db.set_value(doctype, docname, "eway_bill_cancelled", 1)
+def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
 
 
 @frappe.whitelist()
diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
index 09f2df1..3f42668 100644
--- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
+++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py
@@ -32,7 +32,7 @@
 	added_item = []
 	for d in item_list:
 		if (d.parent, d.item_code) not in added_item:
-			row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
+			row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty, d.tax_rate]
 			total_tax = 0
 			for tax in tax_columns:
 				item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
@@ -40,11 +40,9 @@
 
 			row += [d.base_net_amount + total_tax]
 			row += [d.base_net_amount]
-
 			for tax in tax_columns:
 				item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
 				row += [item_tax.get("tax_amount", 0)]
-
 			data.append(row)
 			added_item.append((d.parent, d.item_code))
 	if data:
@@ -64,6 +62,7 @@
 		{"fieldname": "description", "label": _("Description"), "fieldtype": "Data", "width": 300},
 		{"fieldname": "stock_uom", "label": _("Stock UOM"), "fieldtype": "Data", "width": 100},
 		{"fieldname": "stock_qty", "label": _("Stock Qty"), "fieldtype": "Float", "width": 90},
+		{"fieldname": "tax_rate", "label": _("Tax Rate"), "fieldtype": "Data", "width": 90},
 		{"fieldname": "total_amount", "label": _("Total Amount"), "fieldtype": "Currency", "width": 120},
 		{
 			"fieldname": "taxable_amount",
@@ -106,16 +105,25 @@
 			sum(`tabSales Invoice Item`.stock_qty) as stock_qty,
 			sum(`tabSales Invoice Item`.base_net_amount) as base_net_amount,
 			sum(`tabSales Invoice Item`.base_price_list_rate) as base_price_list_rate,
-			`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code,
-			`tabGST HSN Code`.description
-		from `tabSales Invoice`, `tabSales Invoice Item`, `tabGST HSN Code`
-		where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
+			`tabSales Invoice Item`.parent,
+			`tabSales Invoice Item`.item_code,
+			`tabGST HSN Code`.description,
+			json_extract(`tabSales Taxes and Charges`.item_wise_tax_detail,
+			concat('$."' , `tabSales Invoice Item`.item_code, '"[0]')) * count(distinct `tabSales Taxes and Charges`.name) as tax_rate
+		from
+			`tabSales Invoice`,
+			`tabSales Invoice Item`,
+			`tabGST HSN Code`,
+			`tabSales Taxes and Charges`
+		where
+			`tabSales Invoice`.name = `tabSales Invoice Item`.parent
+			and `tabSales Taxes and Charges`.parent = `tabSales Invoice`.name
 			and `tabSales Invoice`.docstatus = 1
 			and `tabSales Invoice Item`.gst_hsn_code is not NULL
 			and `tabSales Invoice Item`.gst_hsn_code = `tabGST HSN Code`.name %s %s
 		group by
-			`tabSales Invoice Item`.parent, `tabSales Invoice Item`.item_code
-
+			`tabSales Invoice Item`.parent,
+			`tabSales Invoice Item`.item_code
 		"""
 		% (conditions, match_conditions),
 		filters,
@@ -213,15 +221,16 @@
 	result = []
 
 	for row in data:
-		merged_hsn_dict.setdefault(row[0], {})
+		key = row[0] + "-" + str(row[4])
+		merged_hsn_dict.setdefault(key, {})
 		for i, d in enumerate(columns):
 			if d["fieldtype"] not in ("Int", "Float", "Currency"):
-				merged_hsn_dict[row[0]][d["fieldname"]] = row[i]
+				merged_hsn_dict[key][d["fieldname"]] = row[i]
 			else:
-				if merged_hsn_dict.get(row[0], {}).get(d["fieldname"], ""):
-					merged_hsn_dict[row[0]][d["fieldname"]] += row[i]
+				if merged_hsn_dict.get(key, {}).get(d["fieldname"], ""):
+					merged_hsn_dict[key][d["fieldname"]] += row[i]
 				else:
-					merged_hsn_dict[row[0]][d["fieldname"]] = row[i]
+					merged_hsn_dict[key][d["fieldname"]] = row[i]
 
 	for key, value in merged_hsn_dict.items():
 		result.append(value)
@@ -240,7 +249,7 @@
 
 	fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
 
-	gst_json = {"version": "GST2.3.4", "hash": "hash", "gstin": gstin, "fp": fp}
+	gst_json = {"version": "GST3.0.3", "hash": "hash", "gstin": gstin, "fp": fp}
 
 	gst_json["hsn"] = {"data": get_hsn_wise_json_data(filters, report_data)}
 
@@ -271,7 +280,7 @@
 			"desc": hsn.get("description"),
 			"uqc": hsn.get("stock_uom").upper(),
 			"qty": hsn.get("stock_qty"),
-			"val": flt(hsn.get("total_amount"), 2),
+			"rt": flt(hsn.get("tax_rate"), 2),
 			"txval": flt(hsn.get("taxable_amount", 2)),
 			"iamt": 0.0,
 			"camt": 0.0,
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 7a68386..cb4bd51 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -479,16 +479,20 @@
 		frappe.dom.freeze();
 		this.frm = this.get_new_frm(this.frm);
 		this.frm.doc.items = [];
-		const res = await frappe.call({
+		return frappe.call({
 			method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
 			args: {
 				'source_name': doc.name,
 				'target_doc': this.frm.doc
+			},
+			callback: (r) => {
+				frappe.model.sync(r.message);
+				frappe.get_doc(r.message.doctype, r.message.name).__run_link_triggers = false;
+				this.set_pos_profile_data().then(() => {
+					frappe.dom.unfreeze();
+				});
 			}
 		});
-		frappe.model.sync(res.message);
-		await this.set_pos_profile_data();
-		frappe.dom.unfreeze();
 	}
 
 	set_pos_profile_data() {
diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
index 091c20c..e10df2a 100644
--- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
+++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py
@@ -238,4 +238,5 @@
 			"datasets": [{"name": _("Total Sales Amount"), "values": datapoints[:30]}],
 		},
 		"type": "bar",
+		"fieldtype": "Currency",
 	}
diff --git a/erpnext/selling/report/quotation_trends/quotation_trends.py b/erpnext/selling/report/quotation_trends/quotation_trends.py
index 4e0758d..4d71ce7 100644
--- a/erpnext/selling/report/quotation_trends/quotation_trends.py
+++ b/erpnext/selling/report/quotation_trends/quotation_trends.py
@@ -54,4 +54,5 @@
 		},
 		"type": "line",
 		"lineOptions": {"regionFill": 1},
+		"fieldtype": "Currency",
 	}
diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py
index 1a2476a..9d7d806 100644
--- a/erpnext/selling/report/sales_analytics/sales_analytics.py
+++ b/erpnext/selling/report/sales_analytics/sales_analytics.py
@@ -415,3 +415,8 @@
 		else:
 			labels = [d.get("label") for d in self.columns[1 : length - 1]]
 		self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"}
+
+		if self.filters["value_quantity"] == "Value":
+			self.chart["fieldtype"] = "Currency"
+		else:
+			self.chart["fieldtype"] = "Float"
diff --git a/erpnext/selling/report/sales_order_trends/sales_order_trends.py b/erpnext/selling/report/sales_order_trends/sales_order_trends.py
index 719f1c5..18f448c 100644
--- a/erpnext/selling/report/sales_order_trends/sales_order_trends.py
+++ b/erpnext/selling/report/sales_order_trends/sales_order_trends.py
@@ -51,4 +51,5 @@
 		},
 		"type": "line",
 		"lineOptions": {"regionFill": 1},
+		"fieldtype": "Currency",
 	}
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index 32c58c5..b8f4803 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -16,6 +16,9 @@
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
+	EmptyStockReconciliationItemsError,
+)
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
 	create_stock_reconciliation,
 )
@@ -180,9 +183,12 @@
 		if not frappe.db.exists("Item", item_code):
 			create_item(item_code)
 
-	create_stock_reconciliation(
-		item_code="Test FG A RW 1", warehouse="_Test Warehouse - _TC", qty=10, rate=2000
-	)
+	try:
+		create_stock_reconciliation(
+			item_code="Test FG A RW 1", warehouse="_Test Warehouse - _TC", qty=10, rate=2000
+		)
+	except EmptyStockReconciliationItemsError:
+		pass
 
 	if frappe.db.exists("Item", "Test FG A RW 1"):
 		doc = frappe.get_doc("Item", "Test FG A RW 1")
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 3ccd342..b9c57c1 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -652,6 +652,104 @@
 		serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
 		self.assertFalse(frappe.db.get_value("Serial No", serial_no, "warehouse"))
 
+	def test_serial_batch_item_stock_entry(self):
+		"""
+		Behaviour: 1) Submit Stock Entry (Receipt) with Serial & Batched Item
+		2) Cancel same Stock Entry
+		Expected Result: 1) Batch is created with Reference in Serial No
+		2) Batch is deleted and Serial No is Inactive
+		"""
+		from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+		item = frappe.db.exists("Item", {"item_name": "Batched and Serialised Item"})
+		if not item:
+			item = create_item("Batched and Serialised Item")
+			item.has_batch_no = 1
+			item.create_new_batch = 1
+			item.has_serial_no = 1
+			item.batch_number_series = "B-BATCH-.##"
+			item.serial_no_series = "S-.####"
+			item.save()
+		else:
+			item = frappe.get_doc("Item", {"item_name": "Batched and Serialised Item"})
+
+		se = make_stock_entry(
+			item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100
+		)
+		batch_no = se.items[0].batch_no
+		serial_no = get_serial_nos(se.items[0].serial_no)[0]
+		batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+
+		batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
+		self.assertEqual(batch_in_serial_no, batch_no)
+
+		self.assertEqual(batch_qty, 1)
+
+		se.cancel()
+
+		batch_in_serial_no = frappe.db.get_value("Serial No", serial_no, "batch_no")
+		self.assertEqual(batch_in_serial_no, None)
+
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Inactive")
+		self.assertEqual(frappe.db.exists("Batch", batch_no), None)
+
+	def test_serial_batch_item_qty_deduction(self):
+		"""
+		Behaviour: Create 2 Stock Entries, both adding Serial Nos to same batch
+		Expected: 1) Cancelling first Stock Entry (origin transaction of created batch)
+		should throw a LinkExistsError
+		2) Cancelling second Stock Entry should make Serial Nos that are, linked to mentioned batch
+		and in that transaction only, Inactive.
+		"""
+		from erpnext.stock.doctype.batch.batch import get_batch_qty
+
+		item = frappe.db.exists("Item", {"item_name": "Batched and Serialised Item"})
+		if not item:
+			item = create_item("Batched and Serialised Item")
+			item.has_batch_no = 1
+			item.create_new_batch = 1
+			item.has_serial_no = 1
+			item.batch_number_series = "B-BATCH-.##"
+			item.serial_no_series = "S-.####"
+			item.save()
+		else:
+			item = frappe.get_doc("Item", {"item_name": "Batched and Serialised Item"})
+
+		se1 = make_stock_entry(
+			item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100
+		)
+		batch_no = se1.items[0].batch_no
+		serial_no1 = get_serial_nos(se1.items[0].serial_no)[0]
+
+		# Check Source (Origin) Document of Batch
+		self.assertEqual(frappe.db.get_value("Batch", batch_no, "reference_name"), se1.name)
+
+		se2 = make_stock_entry(
+			item_code=item.item_code,
+			target="_Test Warehouse - _TC",
+			qty=1,
+			basic_rate=100,
+			batch_no=batch_no,
+		)
+		serial_no2 = get_serial_nos(se2.items[0].serial_no)[0]
+
+		batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+		self.assertEqual(batch_qty, 2)
+
+		se2.cancel()
+
+		# Check decrease in Batch Qty
+		batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
+		self.assertEqual(batch_qty, 1)
+
+		# Check if Serial No from Stock Entry 1 is intact
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "batch_no"), batch_no)
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no1, "status"), "Active")
+
+		# Check if Serial No from Stock Entry 2 is Unlinked and Inactive
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "batch_no"), None)
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "status"), "Inactive")
+
 	def test_warehouse_company_validation(self):
 		company = frappe.db.get_value("Warehouse", "_Test Warehouse 2 - _TC1", "company")
 		frappe.get_doc("User", "test2@example.com").add_roles(
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 5850ec7..4e2fc83 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
@@ -1183,6 +1183,42 @@
 		backdated.cancel()
 		self.assertEqual([1], ordered_qty_after_transaction())
 
+	def test_timestamp_clash(self):
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+
+		reciept = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=100,
+			rate=10,
+			posting_date="2021-01-01",
+			posting_time="01:00:00",
+		)
+
+		consumption = make_stock_entry(
+			item_code=item,
+			from_warehouse=warehouse,
+			qty=50,
+			posting_date="2021-01-01",
+			posting_time="02:00:00.1234",  # ms are possible when submitted without editing posting time
+		)
+
+		backdated_receipt = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=100,
+			posting_date="2021-01-01",
+			rate=10,
+			posting_time="02:00:00",  # same posting time as consumption but ms part stripped
+		)
+
+		try:
+			backdated_receipt.cancel()
+		except Exception as e:
+			self.fail("Double processing of qty for clashing timestamp.")
+
 
 def create_repack_entry(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 5d5a27f..bd60cf0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -62,6 +62,7 @@
 		self.make_sle_on_cancel()
 		self.make_gl_entries_on_cancel()
 		self.repost_future_sle_and_gle()
+		self.delete_auto_created_batches()
 
 	def remove_items_with_no_change(self):
 		"""Remove items if qty or rate is not changed"""
@@ -456,7 +457,7 @@
 
 			key = (d.item_code, d.warehouse)
 			if key not in merge_similar_entries:
-				d.total_amount = d.actual_qty * d.valuation_rate
+				d.total_amount = flt(d.actual_qty) * d.valuation_rate
 				merge_similar_entries[key] = d
 			elif d.serial_no:
 				data = merge_similar_entries[key]
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 1e59aae..9088eb8 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -31,6 +31,7 @@
 
 	def tearDown(self):
 		frappe.local.future_sle = {}
+		frappe.flags.pop("dont_execute_stock_reposts", None)
 
 	def test_reco_for_fifo(self):
 		self._test_reco_sle_gle("FIFO")
@@ -250,7 +251,7 @@
 		warehouse = "_Test Warehouse for Stock Reco2 - _TC"
 
 		sr = create_stock_reconciliation(
-			item_code=item_code, warehouse=warehouse, qty=5, rate=200, do_not_submit=1
+			item_code=item_code, warehouse=warehouse, qty=5, rate=200, do_not_save=1
 		)
 		sr.save()
 		sr.submit()
@@ -288,6 +289,84 @@
 			stock_doc = frappe.get_doc("Stock Reconciliation", d)
 			stock_doc.cancel()
 
+	def test_stock_reco_for_serial_and_batch_item(self):
+		item = create_item("_TestBatchSerialItemReco")
+		item.has_batch_no = 1
+		item.create_new_batch = 1
+		item.has_serial_no = 1
+		item.batch_number_series = "TBS-BATCH-.##"
+		item.serial_no_series = "TBS-.####"
+		item.save()
+
+		warehouse = "_Test Warehouse for Stock Reco2 - _TC"
+
+		sr = create_stock_reconciliation(item_code=item.item_code, warehouse=warehouse, qty=1, rate=100)
+
+		batch_no = sr.items[0].batch_no
+
+		serial_nos = get_serial_nos(sr.items[0].serial_no)
+		self.assertEqual(len(serial_nos), 1)
+		self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "batch_no"), batch_no)
+
+		sr.cancel()
+
+		self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Inactive")
+		self.assertEqual(frappe.db.exists("Batch", batch_no), None)
+
+	def test_stock_reco_for_serial_and_batch_item_with_future_dependent_entry(self):
+		"""
+		Behaviour: 1) Create Stock Reconciliation, which will be the origin document
+		of a new batch having a serial no
+		2) Create a Stock Entry that adds a serial no to the same batch following this
+		Stock Reconciliation
+		3) Cancel Stock Entry
+		Expected Result: 3) Serial No only in the Stock Entry is Inactive and Batch qty decreases
+		"""
+		from erpnext.stock.doctype.batch.batch import get_batch_qty
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+		item = create_item("_TestBatchSerialItemDependentReco")
+		item.has_batch_no = 1
+		item.create_new_batch = 1
+		item.has_serial_no = 1
+		item.batch_number_series = "TBSD-BATCH-.##"
+		item.serial_no_series = "TBSD-.####"
+		item.save()
+
+		warehouse = "_Test Warehouse for Stock Reco2 - _TC"
+
+		stock_reco = create_stock_reconciliation(
+			item_code=item.item_code, warehouse=warehouse, qty=1, rate=100
+		)
+		batch_no = stock_reco.items[0].batch_no
+		reco_serial_no = get_serial_nos(stock_reco.items[0].serial_no)[0]
+
+		stock_entry = make_stock_entry(
+			item_code=item.item_code, target=warehouse, qty=1, basic_rate=100, batch_no=batch_no
+		)
+		serial_no_2 = get_serial_nos(stock_entry.items[0].serial_no)[0]
+
+		# Check Batch qty after 2 transactions
+		batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
+		self.assertEqual(batch_qty, 2)
+
+		# Cancel latest stock document
+		stock_entry.cancel()
+
+		# Check Batch qty after cancellation
+		batch_qty = get_batch_qty(batch_no, warehouse, item.item_code)
+		self.assertEqual(batch_qty, 1)
+
+		# Check if Serial No from Stock Reconcilation is intact
+		self.assertEqual(frappe.db.get_value("Serial No", reco_serial_no, "batch_no"), batch_no)
+		self.assertEqual(frappe.db.get_value("Serial No", reco_serial_no, "status"), "Active")
+
+		# Check if Serial No from Stock Entry is Unlinked and Inactive
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "batch_no"), None)
+		self.assertEqual(frappe.db.get_value("Serial No", serial_no_2, "status"), "Inactive")
+
+		stock_reco.cancel()
+
 	def test_customer_provided_items(self):
 		item_code = "Stock-Reco-customer-Item-100"
 		create_item(
@@ -306,6 +385,7 @@
 		-------------------------------------------
 		Var		| Doc	|	Qty	| Balance
 		-------------------------------------------
+		PR5     | PR    |   10  |  10   (posting date: today-4) [backdated]
 		SR5		| Reco	|	0	|	8	(posting date: today-4) [backdated]
 		PR1		| PR	|	10	|	18	(posting date: today-3)
 		PR2		| PR	|	1	|	19	(posting date: today-2)
@@ -315,6 +395,14 @@
 		item_code = make_item().name
 		warehouse = "_Test Warehouse - _TC"
 
+		frappe.flags.dont_execute_stock_reposts = True
+
+		def assertBalance(doc, qty_after_transaction):
+			sle_balance = frappe.db.get_value(
+				"Stock Ledger Entry", {"voucher_no": doc.name, "is_cancelled": 0}, "qty_after_transaction"
+			)
+			self.assertEqual(sle_balance, qty_after_transaction)
+
 		pr1 = make_purchase_receipt(
 			item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
 		)
@@ -324,62 +412,37 @@
 		pr3 = make_purchase_receipt(
 			item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=nowdate()
 		)
-
-		pr1_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		pr3_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		self.assertEqual(pr1_balance, 10)
-		self.assertEqual(pr3_balance, 12)
+		assertBalance(pr1, 10)
+		assertBalance(pr3, 12)
 
 		# post backdated stock reco in between
 		sr4 = create_stock_reconciliation(
 			item_code=item_code, warehouse=warehouse, qty=6, rate=100, posting_date=add_days(nowdate(), -1)
 		)
-		pr3_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		self.assertEqual(pr3_balance, 7)
+		assertBalance(pr3, 7)
 
 		# post backdated stock reco at the start
 		sr5 = create_stock_reconciliation(
 			item_code=item_code, warehouse=warehouse, qty=8, rate=100, posting_date=add_days(nowdate(), -4)
 		)
-		pr1_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
+		assertBalance(pr1, 18)
+		assertBalance(pr2, 19)
+		assertBalance(sr4, 6)  # check if future stock reco is unaffected
+
+		# Make a backdated receipt and check only entries till first SR are affected
+		pr5 = make_purchase_receipt(
+			item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -5)
 		)
-		pr2_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		sr4_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		self.assertEqual(pr1_balance, 18)
-		self.assertEqual(pr2_balance, 19)
-		self.assertEqual(sr4_balance, 6)  # check if future stock reco is unaffected
+		assertBalance(pr5, 10)
+		# check if future stock reco is unaffected
+		assertBalance(sr4, 6)
+		assertBalance(sr5, 8)
 
 		# cancel backdated stock reco and check future impact
 		sr5.cancel()
-		pr1_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		pr2_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		sr4_balance = frappe.db.get_value(
-			"Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
-		)
-		self.assertEqual(pr1_balance, 10)
-		self.assertEqual(pr2_balance, 11)
-		self.assertEqual(sr4_balance, 6)  # check if future stock reco is unaffected
-
-		# teardown
-		sr4.cancel()
-		pr3.cancel()
-		pr2.cancel()
-		pr1.cancel()
+		assertBalance(pr1, 10)
+		assertBalance(pr2, 11)
+		assertBalance(sr4, 6)  # check if future stock reco is unaffected
 
 	@change_settings("Stock Settings", {"allow_negative_stock": 0})
 	def test_backdated_stock_reco_future_negative_stock(self):
@@ -485,7 +548,6 @@
 
 		# repost will make this test useless, qty should update in realtime without reposts
 		frappe.flags.dont_execute_stock_reposts = True
-		self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
 
 		item_code = make_item().name
 		warehouse = "_Test Warehouse - _TC"
@@ -684,11 +746,13 @@
 		},
 	)
 
-	try:
-		if not args.do_not_submit:
-			sr.submit()
-	except EmptyStockReconciliationItemsError:
-		pass
+	if not args.do_not_save:
+		sr.insert()
+		try:
+			if not args.do_not_submit:
+				sr.submit()
+		except EmptyStockReconciliationItemsError:
+			pass
 	return sr
 
 
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 837c4a6..ed0e2fc 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
@@ -111,17 +111,17 @@
 		},
 		{
 			"fieldname": "posting_date",
-			"fieldtype": "Date",
+			"fieldtype": "Data",
 			"label": _("Posting Date"),
 		},
 		{
 			"fieldname": "posting_time",
-			"fieldtype": "Time",
+			"fieldtype": "Data",
 			"label": _("Posting Time"),
 		},
 		{
 			"fieldname": "creation",
-			"fieldtype": "Datetime",
+			"fieldtype": "Data",
 			"label": _("Creation"),
 		},
 		{
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 7e5c231..4789b52 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1303,6 +1303,8 @@
 	datetime_limit_condition = ""
 	qty_shift = args.actual_qty
 
+	args["time_format"] = "%H:%i:%s"
+
 	# find difference/shift in qty caused by stock reconciliation
 	if args.voucher_type == "Stock Reconciliation":
 		qty_shift = get_stock_reco_qty_shift(args)
@@ -1315,7 +1317,7 @@
 		datetime_limit_condition = get_datetime_limit_condition(detail)
 
 	frappe.db.sql(
-		"""
+		f"""
 		update `tabStock Ledger Entry`
 		set qty_after_transaction = qty_after_transaction + {qty_shift}
 		where
@@ -1323,16 +1325,10 @@
 			and warehouse = %(warehouse)s
 			and voucher_no != %(voucher_no)s
 			and is_cancelled = 0
-			and (timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)
-				or (
-					timestamp(posting_date, posting_time) = timestamp(%(posting_date)s, %(posting_time)s)
-					and creation > %(creation)s
-				)
-			)
+			and timestamp(posting_date, time_format(posting_time, %(time_format)s))
+				> timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
 		{datetime_limit_condition}
-		""".format(
-			qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition
-		),
+		""",
 		args,
 	)
 
@@ -1383,6 +1379,7 @@
 					and creation > %(creation)s
 				)
 			)
+		order by timestamp(posting_date, posting_time) asc, creation asc
 		limit 1
 	""",
 		args,