Merge branch 'develop' into auto-attendance-issue
diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict
index 4c7f5f8..c8337a9 100644
--- a/.github/helper/.flake8_strict
+++ b/.github/helper/.flake8_strict
@@ -65,6 +65,11 @@
     E713,
     E712,
 
+enable-extensions =
+    M90
+
+select =
+    M511
 
 max-line-length = 200
 exclude=.github/helper/semgrep_rules,test_*.py
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 658892c..d765f04 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -104,6 +104,8 @@
 
       - name: Build Assets
         run: cd ~/frappe-bench/ && bench build
+        env:
+          CI: Yes
 
       - name: UI Tests
         run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2b3a471..e411f11 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,10 @@
     rev: 3.9.2
     hooks:
       - id: flake8
-        args: ['--config', '.github/helper/.flake8_strict']
+        additional_dependencies: [
+          'flake8-mutable',
+        ]
+        args: ['--select=M511', '--config', '.github/helper/.flake8_strict']
         exclude: ".*setup.py$"
 
   - repo: https://github.com/timothycrosley/isort
diff --git a/README.md b/README.md
index 847904d..87d7d73 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,12 @@
 
 ---
 
+## Learning
+
+1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
+
+---
+
 ## Logo and Trademark
 
 The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
index 66a269e..d61f8a6 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
@@ -10,6 +10,15 @@
 		// make company mandatory
 		frm.set_df_property('company', 'reqd', frm.doc.company ? 0 : 1);
 		frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1);
+
+		if (frm.doc.import_file) {
+			frappe.run_serially([
+				() => generate_tree_preview(frm),
+				() => create_import_button(frm),
+				() => frm.set_df_property('chart_preview', 'hidden', 0)
+			]);
+		}
+
 		frm.set_df_property('chart_preview', 'hidden',
 			$(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1);
 	},
@@ -72,13 +81,6 @@
 		if (!frm.doc.import_file) {
 			frm.page.set_indicator("");
 			$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
-		} else {
-			frappe.run_serially([
-				() => validate_coa(frm),
-				() => generate_tree_preview(frm),
-				() => create_import_button(frm),
-				() => frm.set_df_property('chart_preview', 'hidden', 0),
-			]);
 		}
 	},
 
@@ -104,26 +106,24 @@
 });
 
 var create_import_button = function(frm) {
-	if (frm.page.show_import_button) {
-		frm.page.set_primary_action(__("Import"), function () {
-			return frappe.call({
-				method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
-				args: {
-					file_name: frm.doc.import_file,
-					company: frm.doc.company
-				},
-				freeze: true,
-				freeze_message: __("Creating Accounts..."),
-				callback: function(r) {
-					if (!r.exc) {
-						clearInterval(frm.page["interval"]);
-						frm.page.set_indicator(__('Import Successful'), 'blue');
-						create_reset_button(frm);
-					}
+	frm.page.set_primary_action(__("Import"), function () {
+		return frappe.call({
+			method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
+			args: {
+				file_name: frm.doc.import_file,
+				company: frm.doc.company
+			},
+			freeze: true,
+			freeze_message: __("Creating Accounts..."),
+			callback: function(r) {
+				if (!r.exc) {
+					clearInterval(frm.page["interval"]);
+					frm.page.set_indicator(__('Import Successful'), 'blue');
+					create_reset_button(frm);
 				}
-			});
-		}).addClass('btn btn-primary');
-	}
+			}
+		});
+	}).addClass('btn btn-primary');
 };
 
 var create_reset_button = function(frm) {
@@ -137,7 +137,6 @@
 var validate_coa = function(frm) {
 	if (frm.doc.import_file) {
 		let parent = __('All Accounts');
-
 		return frappe.call({
 			'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
 			'args': {
@@ -157,25 +156,23 @@
 };
 
 var generate_tree_preview = function(frm) {
-	if (frm.doc.import_file) {
-		let parent = __('All Accounts');
-		$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
+	let parent = __('All Accounts');
+	$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
 
-		// generate tree structure based on the csv data
-		return new frappe.ui.Tree({
-			parent: $(frm.fields_dict['chart_tree'].wrapper),
-			label: parent,
-			expandable: true,
-			method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
-			args: {
-				file_name: frm.doc.import_file,
-				parent: parent,
-				doctype: 'Chart of Accounts Importer',
-				file_type: frm.doc.file_type
-			},
-			onclick: function(node) {
-				parent = node.value;
-			}
-		});
-	}
+	// generate tree structure based on the csv data
+	return new frappe.ui.Tree({
+		parent: $(frm.fields_dict['chart_tree'].wrapper),
+		label: parent,
+		expandable: true,
+		method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
+		args: {
+			file_name: frm.doc.import_file,
+			parent: parent,
+			doctype: 'Chart of Accounts Importer',
+			file_type: frm.doc.file_type
+		},
+		onclick: function(node) {
+			parent = node.value;
+		}
+	});
 };
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index bd2a6f1..5e596f8 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -25,7 +25,9 @@
 
 
 class ChartofAccountsImporter(Document):
-	pass
+	def validate(self):
+		if self.import_file:
+			get_coa('Chart of Accounts Importer', 'All Accounts', file_name=self.import_file, for_validate=1)
 
 def validate_columns(data):
 	if not data:
@@ -34,7 +36,8 @@
 	no_of_columns = max([len(d) for d in data])
 
 	if no_of_columns > 7:
-		frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'))
+		frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'),
+			title=(_("Wrong Template")))
 
 @frappe.whitelist()
 def validate_company(company):
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index 83ecfb4..7c53f4a 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -33,7 +33,9 @@
 
 		frappe.db.sql("delete from `tabPOS Profile`")
 
-def get_customers_list(pos_profile={}):
+def get_customers_list(pos_profile=None):
+	if pos_profile is None:
+		pos_profile = {}
 	cond = "1=1"
 	customer_groups = []
 	if pos_profile.get('customer_groups'):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 12b486e..0637fda 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -398,7 +398,9 @@
 				pricing_rules[0].apply_rule_on_other_items = items
 				return pricing_rules
 
-def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
+def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
+	if items is None:
+		items = []
 	sum_qty, sum_amt = [0, 0]
 	doctype = doc.get('parenttype') or doc.doctype
 
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index d09f7dc..f5391ca 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -69,7 +69,9 @@
 			{'promotional_scheme': self.name}):
 			frappe.delete_doc('Pricing Rule', rule.name)
 
-def get_pricing_rules(doc, rules = {}):
+def get_pricing_rules(doc, rules=None):
+	if rules is None:
+		rules = {}
 	new_doc = []
 	for child_doc, fields in {'price_discount_slabs': price_discount_fields,
 		'product_discount_slabs': product_discount_fields}.items():
@@ -78,7 +80,9 @@
 
 	return new_doc
 
-def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
+def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
+	if rules is None:
+		rules = {}
 	new_doc = []
 	args = get_args_for_pricing_rule(doc)
 	applicable_for = frappe.scrub(doc.get('applicable_for'))
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index eb26aa2..d909814 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -232,9 +232,6 @@
 		if self.update_stock == 1:
 			self.repost_future_sle_and_gle()
 
-		if self.update_stock == 1:
-			self.repost_future_sle_and_gle()
-
 		if not self.is_return:
 			self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 8a2e945..f492a03 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2023,11 +2023,7 @@
 		frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
 
-	def test_sle_if_target_warehouse_exists_accidentally(self):
-		"""
-			Check if inward entry exists if Target Warehouse accidentally exists
-			but Customer is not an internal customer.
-		"""
+	def test_sle_for_target_warehouse(self):
 		se = make_stock_entry(
 			item_code="138-CMS Shoe",
 			target="Finished Goods - _TC",
@@ -2048,9 +2044,9 @@
 		sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name},
 			fields=["name", "actual_qty"])
 
-		# check if only one SLE for outward entry is created
-		self.assertEqual(len(sles), 1)
-		self.assertEqual(sles[0].actual_qty, -1)
+		# check if both SLEs are created
+		self.assertEqual(len(sles), 2)
+		self.assertEqual(sum(d.actual_qty for d in sles), 0.0)
 
 		# tear down
 		si.cancel()
diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py
index d527188..bb8138b 100644
--- a/erpnext/accounts/report/cash_flow/cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/cash_flow.py
@@ -139,9 +139,9 @@
 	data["total"] = total
 	return data
 
-def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters={}):
+def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None):
 	cond = ""
-	filters = frappe._dict(filters)
+	filters = frappe._dict(filters or {})
 
 	if filters.include_default_book_entries:
 		company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 0158a11..bb269f3 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -424,7 +424,7 @@
 					or (cint(self.is_return) and self.docstatus==2)):
 						sl_entries.append(self.get_sle_for_source_warehouse(d))
 
-				if d.target_warehouse and self.get("is_internal_customer"):
+				if d.target_warehouse:
 					sl_entries.append(self.get_sle_for_target_warehouse(d))
 
 				if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
@@ -559,6 +559,12 @@
 				frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
 					.format(d.idx, warehouse, warehouse))
 
+		if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
+			msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
+			msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
+			frappe.msgprint(msg, title="Internal Transfer", alert=True)
+
+
 	def validate_items(self):
 		# validate items to see if they have is_sales_item enabled
 		from erpnext.controllers.buying_controller import validate_item_type
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 95cf032..999599c 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -51,7 +51,7 @@
 		}
 	}
 
-	add_lead_to_prospect (frm) {
+	add_lead_to_prospect () {
 		frappe.prompt([
 			{
 				fieldname: 'prospect',
@@ -65,7 +65,7 @@
 			frappe.call({
 				method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect',
 				args: {
-					'lead': frm.doc.name,
+					'lead': cur_frm.doc.name,
 					'prospect': data.prospect
 				},
 				callback: function(r) {
@@ -79,41 +79,41 @@
 		}, __('Add Lead to Prospect'), __('Add'));
 	}
 
-	make_customer (frm) {
+	make_customer () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_customer",
-			frm: frm
+			frm: cur_frm
 		})
 	}
 
-	make_opportunity (frm) {
+	make_opportunity () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_opportunity",
-			frm: frm
+			frm: cur_frm
 		})
 	}
 
-	make_quotation (frm) {
+	make_quotation () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_quotation",
-			frm: frm
+			frm: cur_frm
 		})
 	}
 
-	make_prospect (frm) {
+	make_prospect () {
 		frappe.model.with_doctype("Prospect", function() {
 			let prospect = frappe.model.get_new_doc("Prospect");
-			prospect.company_name = frm.doc.company_name;
-			prospect.no_of_employees = frm.doc.no_of_employees;
-			prospect.industry = frm.doc.industry;
-			prospect.market_segment = frm.doc.market_segment;
-			prospect.territory = frm.doc.territory;
-			prospect.fax = frm.doc.fax;
-			prospect.website = frm.doc.website;
-			prospect.prospect_owner = frm.doc.lead_owner;
+			prospect.company_name = cur_frm.doc.company_name;
+			prospect.no_of_employees = cur_frm.doc.no_of_employees;
+			prospect.industry = cur_frm.doc.industry;
+			prospect.market_segment = cur_frm.doc.market_segment;
+			prospect.territory = cur_frm.doc.territory;
+			prospect.fax = cur_frm.doc.fax;
+			prospect.website = cur_frm.doc.website;
+			prospect.prospect_owner = cur_frm.doc.lead_owner;
 
 			let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead');
-			lead_prospect_row.lead = frm.doc.name;
+			lead_prospect_row.lead = cur_frm.doc.name;
 
 			frappe.set_route("Form", "Prospect", prospect.name);
 		});
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index ae498ba..be4ee56 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -138,7 +138,9 @@
 			enrollment.submit()
 			return enrollment
 
-	def enroll_in_course(self, course_name, program_enrollment, enrollment_date=frappe.utils.datetime.datetime.now()):
+	def enroll_in_course(self, course_name, program_enrollment, enrollment_date=None):
+		if enrollment_date is None:
+			enrollment_date = frappe.utils.datetime.datetime.now()
 		try:
 			enrollment = frappe.get_doc({
 					"doctype": "Course Enrollment",
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index 9cb65f7..941fd58 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -176,7 +176,7 @@
 	account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account)
 	return {'taxes':[{
 		"account_head": account,
-		"rate": 0,
+		"rate": 9,
 		"description": "CGST",
 		"tax_amount": 10,
 		"total": 210
diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
index 020457d..4a1064b 100644
--- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
+++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json
@@ -56,8 +56,6 @@
   },
   {
    "columns": 2,
-   "fetch_from": "account_head.tax_rate",
-   "fetch_if_empty": 1,
    "fieldname": "rate",
    "fieldtype": "Float",
    "in_list_view": 1,
@@ -111,4 +109,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py
index f46f14d..7d1b991 100644
--- a/erpnext/hr/doctype/holiday_list/holiday_list.py
+++ b/erpnext/hr/doctype/holiday_list/holiday_list.py
@@ -1,4 +1,3 @@
-
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
@@ -94,9 +93,11 @@
 		update={"allDay": 1})
 
 
-def is_holiday(holiday_list, date=today()):
+def is_holiday(holiday_list, date=None):
 	"""Returns true if the given date is a holiday in the given holiday list
 	"""
+	if date is None:
+		date = today()
 	if holiday_list:
 		return bool(frappe.get_all('Holiday List',
 			dict(name=holiday_list, holiday_date=date)))
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index 69af5c5..05b74a0 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -139,7 +139,7 @@
 	return shift_timing_map
 
 
-def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None):
+def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None):
 	"""Returns a Shift Type for the given employee on the given date. (excluding the holidays)
 
 	:param employee: Employee for which shift is required.
@@ -147,6 +147,8 @@
 	:param consider_default_shift: If set to true, default shift is taken when no shift assignment is found.
 	:param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date.
 	"""
+	if for_date is None:
+		for_date = nowdate()
 	default_shift = frappe.db.get_value('Employee', employee, 'default_shift')
 	shift_type_name = None
 	shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date'])
@@ -200,9 +202,11 @@
 	return get_shift_details(shift_type_name, for_date)
 
 
-def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False):
+def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False):
 	"""Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee
 	"""
+	if for_timestamp is None:
+		for_timestamp = now_datetime()
 	# write and verify a test case for midnight shift.
 	prev_shift = curr_shift = next_shift = None
 	curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward')
@@ -220,7 +224,7 @@
 	return prev_shift, curr_shift, next_shift
 
 
-def get_shift_details(shift_type_name, for_date=nowdate()):
+def get_shift_details(shift_type_name, for_date=None):
 	"""Returns Shift Details which contain some additional information as described below.
 	'shift_details' contains the following keys:
 		'shift_type' - Object of DocType Shift Type,
@@ -234,6 +238,8 @@
 	"""
 	if not shift_type_name:
 		return None
+	if not for_date:
+		for_date = nowdate()
 	shift_type = frappe.get_doc('Shift Type', shift_type_name)
 	start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time
 	for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 57a92b0..93cd4e1 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -155,7 +155,11 @@
 	return employee_counts
 
 @frappe.whitelist()
-def get_active_staffing_plan_details(company, designation, from_date=getdate(nowdate()), to_date=getdate(nowdate())):
+def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None):
+	if from_date is None:
+		from_date = getdate(nowdate())
+	if to_date is None:
+		to_date = getdate(nowdate())
 	if not company or not designation:
 		frappe.throw(_("Please select Company and Designation"))
 
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 0bf5aea..a1df9cf 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -47,7 +47,7 @@
 			"Yearly": 365
 		}
 		for item in self.items:
-			if item.periodicity and item.start_date:
+			if item.periodicity and item.periodicity != "Random" and item.start_date:
 				if not item.end_date:
 					if item.no_of_visits:
 						item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity])
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 9ed6686..178cd5c 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -141,7 +141,6 @@
 			create_salary_structure_assignment,
 		)
 
-		no_of_days = self.get_no_of_days()
 		# Payroll based on attendance
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
 
@@ -168,9 +167,6 @@
 		ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name)
 		self.assertEqual(ss.absent_days, 1)
 
-		days_in_month = no_of_days[0]
-		no_of_holidays = no_of_days[1]
-
 		ss.reload()
 		payment_days_based_comp_amount = 0
 		for component in ss.earnings:
@@ -992,13 +988,14 @@
 	return salary_structure_doc
 
 def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure):
-	employee = frappe.db.get_value("Employee", {
-			"user_id": employee
-		},
+	employee = frappe.db.get_value(
+		"Employee",
+		{"user_id": employee},
 		["name", "company", "employee_name"],
-		as_dict=True)
+		as_dict=True
+	)
 
-	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})})
+	salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": employee.name})
 
 	if not salary_slip_name:
 		salary_slip = make_salary_slip(salary_structure, employee=employee.name)
@@ -1009,4 +1006,4 @@
 	else:
 		salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
 
-	return salary_slip
\ No newline at end of file
+	return salary_slip
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 8403193..95ca386 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -46,43 +46,6 @@
 		});
 	},
 
-	change_abbreviation(frm) {
-		var dialog = new frappe.ui.Dialog({
-			title: "Replace Abbr",
-			fields: [
-				{"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr",
-					"reqd": 1 },
-				{"fieldtype": "Button", "label": "Update", "fieldname": "update"},
-			]
-		});
-
-		dialog.fields_dict.update.$input.click(function() {
-			var args = dialog.get_values();
-			if (!args) return;
-			frappe.show_alert(__("Update in progress. It might take a while."));
-			return frappe.call({
-				method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr",
-				args: {
-					"company": frm.doc.name,
-					"old": frm.doc.abbr,
-					"new": args.new_abbr
-				},
-				callback: function(r) {
-					if (r.exc) {
-						frappe.msgprint(__("There were errors."));
-						return;
-					} else {
-						frm.set_value("abbr", args.new_abbr);
-					}
-					dialog.hide();
-					frm.refresh();
-				},
-				btn: this
-			});
-		});
-		dialog.show();
-	},
-
 	company_name: function(frm) {
 		if(frm.doc.__islocal) {
 			// add missing " " arg in split method
@@ -164,10 +127,6 @@
 					}, __('Manage'));
 				}
 			}
-
-			frm.add_custom_button(__('Change Abbreviation'), () => {
-				frm.trigger('change_abbreviation');
-			}, __('Manage'));
 		}
 
 		erpnext.company.set_chart_of_accounts_options(frm.doc);
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 58cb52c..63d96bf 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -125,7 +125,8 @@
    "label": "Abbr",
    "oldfieldname": "abbr",
    "oldfieldtype": "Data",
-   "reqd": 1
+   "reqd": 1,
+   "set_only_once": 1
   },
   {
    "bold": 1,
@@ -747,10 +748,11 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2021-07-12 11:27:06.353860",
+ "modified": "2021-10-04 12:09:25.833133",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",
+ "naming_rule": "By fieldname",
  "nsm_parent_field": "parent_company",
  "owner": "Administrator",
  "permissions": [
@@ -808,4 +810,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 87d67a5..0b1b4a1 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -399,44 +399,6 @@
 		if not frappe.db.get_value('GL Entry', {'company': self.name}):
 			frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name)
 
-@frappe.whitelist()
-def enqueue_replace_abbr(company, old, new):
-	kwargs = dict(queue="long", company=company, old=old, new=new)
-	frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
-
-
-@frappe.whitelist()
-def replace_abbr(company, old, new):
-	new = new.strip()
-	if not new:
-		frappe.throw(_("Abbr can not be blank or space"))
-
-	frappe.only_for("System Manager")
-
-	def _rename_record(doc):
-		parts = doc[0].rsplit(" - ", 1)
-		if len(parts) == 1 or parts[1].lower() == old.lower():
-			frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True)
-
-	def _rename_records(dt):
-		# rename is expensive so let's be economical with memory usage
-		doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company))
-		for d in doc:
-			_rename_record(d)
-	try:
-		frappe.db.auto_commit_on_many_writes = 1
-		for dt in ["Warehouse", "Account", "Cost Center", "Department",
-				"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
-			_rename_records(dt)
-			frappe.db.commit()
-		frappe.db.set_value("Company", company, "abbr", new)
-
-	except Exception:
-		frappe.log_error(title=_('Abbreviation Rename Error'))
-	finally:
-		frappe.db.auto_commit_on_many_writes = 0
-
-
 def get_name_with_abbr(name, company):
 	company_abbr = frappe.get_cached_value('Company',  company,  "abbr")
 	parts = name.split(" - ")
diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py
index faa25df..58a14d2 100644
--- a/erpnext/setup/setup_wizard/operations/taxes_setup.py
+++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py
@@ -192,7 +192,7 @@
 	default_root_type = 'Liability'
 	root_type = account.get('root_type', default_root_type)
 
-	existing_accounts = frappe.get_list('Account',
+	existing_accounts = frappe.get_all('Account',
 		filters={
 			'company': company_name,
 			'root_type': root_type
@@ -247,7 +247,7 @@
 
 	# Create a new group account named 'Duties and Taxes' or 'Tax Assets' just
 	# below the root account
-	root_account = frappe.get_list('Account', {
+	root_account = frappe.get_all('Account', {
 		'is_group': 1,
 		'root_type': root_type,
 		'company': company_name,
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 5542cd0..f75b52c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -185,7 +185,6 @@
 			if not d['warehouse'] and frappe.db.get_value("Item", d['item_code'], "is_stock_item") == 1:
 				frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"]))
 
-
 	def update_current_stock(self):
 		if self.get("_action") and self._action != "update_after_submit":
 			for d in self.get('items'):
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index b05090a..a96c299 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -468,7 +468,7 @@
    "width": "100px"
   },
   {
-   "depends_on": "eval:parent.is_internal_customer",
+   "depends_on": "eval:parent.is_internal_customer || doc.target_warehouse",
    "fieldname": "target_warehouse",
    "fieldtype": "Link",
    "hidden": 1,
@@ -759,7 +759,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-23 01:04:08.588104",
+ "modified": "2021-10-05 12:12:44.018872",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
@@ -767,4 +767,4 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC"
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 768e5ea..8cc9f74 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -181,6 +181,8 @@
 				"doctype": "Item Price",
 				"price_list": price_list,
 				"item_code": self.name,
+				"uom": self.stock_uom,
+				"brand": self.brand,
 				"currency": erpnext.get_default_currency(),
 				"price_list_rate": self.standard_rate
 			})
@@ -634,9 +636,21 @@
 				_("An Item Group exists with same name, please change the item name or rename the item group"))
 
 	def update_item_price(self):
-		frappe.db.sql("""update `tabItem Price` set item_name=%s,
-			item_description=%s, brand=%s where item_code=%s""",
-					(self.item_name, self.description, self.brand, self.name))
+		frappe.db.sql("""
+				UPDATE `tabItem Price`
+				SET
+					item_name=%(item_name)s,
+					item_description=%(item_description)s,
+					brand=%(brand)s
+				WHERE item_code=%(item_code)s
+			""",
+			dict(
+				item_name=self.item_name,
+				item_description=self.description,
+				brand=self.brand,
+				item_code=self.name
+			)
+		)
 
 	def on_trash(self):
 		super(Item, self).on_trash()
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index cf98b19..17df977 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -296,7 +296,7 @@
 
 		return d.ordered_qty < d.stock_qty and child_filter
 
-	doclist = get_mapped_doc("Material Request", source_name, 	{
+	doclist = get_mapped_doc("Material Request", source_name, {
 		"Material Request": {
 			"doctype": "Purchase Order",
 			"validation": {
@@ -323,7 +323,7 @@
 
 @frappe.whitelist()
 def make_request_for_quotation(source_name, target_doc=None):
-	doclist = get_mapped_doc("Material Request", source_name, 	{
+	doclist = get_mapped_doc("Material Request", source_name, {
 		"Material Request": {
 			"doctype": "Request for Quotation",
 			"validation": {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 07a568d..47c8df9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -842,7 +842,8 @@
 			"doctype": "Stock Entry Detail",
 			"field_map": {
 				"warehouse": "s_warehouse",
-				"parent": "reference_purchase_receipt"
+				"parent": "reference_purchase_receipt",
+				"batch_no": "batch_no"
 			},
 		},
 	}, target_doc, set_missing_values)
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 82d8aae..a9254fb 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -611,7 +611,9 @@
 
 	return reserved_sr_nos
 
-def fetch_serial_numbers(filters, qty, do_not_include=[]):
+def fetch_serial_numbers(filters, qty, do_not_include=None):
+	if do_not_include is None:
+		do_not_include = []
 	batch_join_selection = ""
 	batch_no_condition = ""
 	batch_nos = filters.get("batch_no")
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 19597c3..cbff214 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -382,7 +382,7 @@
 
 	return out
 
-def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
+def get_item_warehouse(item, args, overwrite_warehouse, defaults=None):
 	if not defaults:
 		defaults = frappe._dict({
 			'item_defaults' : get_item_defaults(item.name, args.company),
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 3cd4cd2..7c6fbfd 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -4,6 +4,7 @@
 from __future__ import unicode_literals
 
 import json
+from math import ceil
 
 import frappe
 from frappe import _
@@ -149,11 +150,16 @@
 							conversion_factor = frappe.db.get_value("UOM Conversion Detail",
 								{'parent': item.name, 'uom': uom}, 'conversion_factor') or 1.0
 
+					must_be_whole_number = frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True)
+					qty = d.reorder_qty / conversion_factor
+					if must_be_whole_number:
+						qty = ceil(qty)
+
 					mr.append("items", {
 						"doctype": "Material Request Item",
 						"item_code": d.item_code,
 						"schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
-						"qty": d.reorder_qty / conversion_factor,
+						"qty": qty,
 						"uom": uom,
 						"stock_uom": item.stock_uom,
 						"warehouse": d.warehouse,
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index 7d7399d..0fe1068 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -228,7 +228,7 @@
 def set_first_response_time(communication, method):
 	if communication.get('reference_doctype') == "Issue":
 		issue = get_parent_doc(communication)
-		if is_first_response(issue):
+		if is_first_response(issue) and issue.service_level_agreement:
 			first_response_time = calculate_first_response_time(issue, get_datetime(issue.first_responded_on))
 			issue.db_set("first_response_time", first_response_time)