Merge pull request #28002 from ankush/dont_recompute_item_tax

fix: dont recompute item wise taxes from front end
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index ac623e9..85f146d 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -37,6 +37,9 @@
 sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
 
 bench get-app erpnext "${GITHUB_WORKSPACE}"
+
+if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
+
 bench start &> bench_run_logs.txt &
 bench --site test_site reinstall --yes
 bench build --app frappe
diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml
index 166e98a..0cf4e78 100644
--- a/.github/helper/semgrep_rules/frappe_correctness.yml
+++ b/.github/helper/semgrep_rules/frappe_correctness.yml
@@ -132,7 +132,6 @@
   languages: [python]
   severity: ERROR
 
-
 - id: frappe-manual-commit
   patterns:
     - pattern: frappe.db.commit()
@@ -149,3 +148,16 @@
         - "**/demo/**"
   languages: [python]
   severity: ERROR
+
+- id: frappe-using-db-sql
+  pattern-either:
+    - pattern: frappe.db.sql(...)
+    - pattern: frappe.db.sql_ddl(...)
+    - pattern: frappe.db.sql_list(...)
+  paths:
+    exclude:
+      - "test_*.py"
+  message: |
+    The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database)
+  languages: [python]
+  severity: ERROR
\ No newline at end of file
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 16e490a..9389eaa 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -10,13 +10,6 @@
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-      - uses: returntocorp/semgrep-action@v1
-        env:
-            SEMGREP_TIMEOUT: 120
-        with:
-            config: >-
-              r/python.lang.correctness
-              .github/helper/semgrep_rules
 
       - name: Set up Python 3.8
         uses: actions/setup-python@v2
@@ -25,3 +18,11 @@
 
       - name: Install and Run Pre-commit
         uses: pre-commit/action@v2.0.3
+
+      - uses: returntocorp/semgrep-action@v1
+        env:
+            SEMGREP_TIMEOUT: 120
+        with:
+            config: >-
+              r/python.lang.correctness
+              .github/helper/semgrep_rules
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
index 4f84b86..77c0aee 100644
--- a/.github/workflows/server-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -91,6 +91,8 @@
 
       - name: Install
         run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
+        env:
+          TYPE: server
 
       - name: Run Tests
         run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js
index 79e08b3..464cce4 100644
--- a/cypress/integration/test_organizational_chart_desktop.js
+++ b/cypress/integration/test_organizational_chart_desktop.js
@@ -24,7 +24,7 @@
 				cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
 				cy.get('@input')
 					.clear({ force: true })
-					.type('Test Org Chart{enter}', { force: true })
+					.type('Test Org Chart{downarrow}{enter}', { force: true })
 					.blur({ force: true });
 			});
 		});
diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js
index 161fae0..971ac6d 100644
--- a/cypress/integration/test_organizational_chart_mobile.js
+++ b/cypress/integration/test_organizational_chart_mobile.js
@@ -25,7 +25,7 @@
 				cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
 				cy.get('@input')
 					.clear({ force: true })
-					.type('Test Org Chart{enter}', { force: true })
+					.type('Test Org Chart{downarrow}{enter}', { force: true })
 					.blur({ force: true });
 			});
 		});
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index d6ccd16..05caafe 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -12,7 +12,7 @@
 from unidecode import unidecode
 
 
-def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
+def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None):
 	chart = custom_chart or get_chart(chart_template, existing_company)
 	if chart:
 		accounts = []
@@ -22,7 +22,7 @@
 				if root_account:
 					root_type = child.get("root_type")
 
-				if account_name not in ["account_number", "account_type",
+				if account_name not in ["account_name", "account_number", "account_type",
 					"root_type", "is_group", "tax_rate"]:
 
 					account_number = cstr(child.get("account_number")).strip()
@@ -35,7 +35,7 @@
 
 					account = frappe.get_doc({
 						"doctype": "Account",
-						"account_name": account_name,
+						"account_name": child.get('account_name') if from_coa_importer else account_name,
 						"company": company,
 						"parent_account": parent,
 						"is_group": is_group,
@@ -213,7 +213,7 @@
 	return (bank_account in accounts)
 
 @frappe.whitelist()
-def build_tree_from_json(chart_template, chart_data=None):
+def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
 	''' get chart template from its folder and parse the json to be rendered as tree '''
 	chart = chart_data or get_chart(chart_template)
 
@@ -226,9 +226,12 @@
 		''' recursively called to form a parent-child based list of dict from chart template '''
 		for account_name, child in iteritems(children):
 			account = {}
-			if account_name in ["account_number", "account_type",\
+			if account_name in ["account_name", "account_number", "account_type",\
 				"root_type", "is_group", "tax_rate"]: continue
 
+			if from_coa_importer:
+				account_name = child['account_name']
+
 			account['parent_account'] = parent
 			account['expandable'] = True if identify_is_group(child) else False
 			account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
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 5e596f8..eabe408 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
@@ -69,7 +69,7 @@
 
 	frappe.local.flags.ignore_root_company_validation = True
 	forest = build_forest(data)
-	create_charts(company, custom_chart=forest)
+	create_charts(company, custom_chart=forest, from_coa_importer=True)
 
 	# trigger on_update for company to reset default accounts
 	set_default_accounts(company)
@@ -148,7 +148,7 @@
 
 	if not for_validate:
 		forest = build_forest(data)
-		accounts = build_tree_from_json("", chart_data=forest) # returns a list of dict in a tree render-able form
+		accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form
 
 		# filter out to show data for the selected node only
 		accounts = [d for d in accounts if d['parent_account']==parent]
@@ -212,11 +212,14 @@
 		if not account_name:
 			error_messages.append("Row {0}: Please enter Account Name".format(line_no))
 
+		name = account_name
 		if account_number:
 			account_number = cstr(account_number).strip()
 			account_name = "{} - {}".format(account_number, account_name)
 
 		charts_map[account_name] = {}
+		charts_map[account_name]['account_name'] = name
+		if account_number: charts_map[account_name]["account_number"] = account_number
 		if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
 		if account_type: charts_map[account_name]["account_type"] = account_type
 		if root_type: charts_map[account_name]["root_type"] = root_type
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index 8afa0ab..9c9f37b 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -120,6 +120,7 @@
   {
    "fieldname": "payments",
    "fieldtype": "Table",
+   "label": "Payment Methods",
    "options": "POS Payment Method",
    "reqd": 1
   },
@@ -377,7 +378,7 @@
    "link_fieldname": "pos_profile"
   }
  ],
- "modified": "2021-02-01 13:52:51.081311",
+ "modified": "2021-10-14 14:17:00.469298",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Profile",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 1c9943f..508f728 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -15,6 +15,7 @@
 from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
 	check_if_return_invoice_linked_with_payment_entry,
+	get_total_in_party_account_currency,
 	is_overdue,
 	unlink_inter_company_doc,
 	update_linked_doc,
@@ -1183,6 +1184,7 @@
 			return
 
 		outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
+		total = get_total_in_party_account_currency(self)
 
 		if not status:
 			if self.docstatus == 2:
@@ -1190,9 +1192,9 @@
 			elif self.docstatus == 1:
 				if self.is_internal_transfer():
 					self.status = 'Internal Transfer'
-				elif is_overdue(self):
+				elif is_overdue(self, total):
 					self.status = "Overdue"
-				elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
+				elif 0 < outstanding_amount < total:
 					self.status = "Partly Paid"
 				elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
 					self.status = "Unpaid"
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index dafae31..40ad7b7 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1427,6 +1427,7 @@
 			return
 
 		outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount"))
+		total = get_total_in_party_account_currency(self)
 
 		if not status:
 			if self.docstatus == 2:
@@ -1434,9 +1435,9 @@
 			elif self.docstatus == 1:
 				if self.is_internal_transfer():
 					self.status = 'Internal Transfer'
-				elif is_overdue(self):
+				elif is_overdue(self, total):
 					self.status = "Overdue"
-				elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")):
+				elif 0 < outstanding_amount < total:
 					self.status = "Partly Paid"
 				elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
 					self.status = "Unpaid"
@@ -1463,27 +1464,42 @@
 		if update:
 			self.db_set('status', self.status, update_modified = update_modified)
 
-def is_overdue(doc):
-	outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
 
+def get_total_in_party_account_currency(doc):
+	total_fieldname = (
+		"grand_total"
+		if doc.disable_rounded_total
+		else "rounded_total"
+	)
+	if doc.party_account_currency != doc.currency:
+		total_fieldname = "base_" + total_fieldname
+
+	return flt(doc.get(total_fieldname), doc.precision(total_fieldname))
+
+def is_overdue(doc, total):
+	outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount"))
 	if outstanding_amount <= 0:
 		return
 
-	grand_total = flt(doc.grand_total, doc.precision("grand_total"))
-	nowdate = getdate()
-	if doc.payment_schedule:
-		# calculate payable amount till date
-		payable_amount = sum(
-			payment.payment_amount
-			for payment in doc.payment_schedule
-			if getdate(payment.due_date) < nowdate
-		)
+	today = getdate()
+	if doc.get('is_pos') or not doc.get('payment_schedule'):
+		return getdate(doc.due_date) < today
 
-		if (grand_total - outstanding_amount) < payable_amount:
-			return True
+	# calculate payable amount till date
+	payment_amount_field = (
+		"base_payment_amount"
+		if doc.party_account_currency != doc.currency
+		else "payment_amount"
+	)
 
-	elif getdate(doc.due_date) < nowdate:
-		return True
+	payable_amount = sum(
+		payment.get(payment_amount_field)
+		for payment in doc.payment_schedule
+		if getdate(payment.due_date) < today
+	)
+
+	return (total - outstanding_amount) < payable_amount
+
 
 def get_discounting_status(sales_invoice):
 	status = None
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 16ef5fc..c3cb839 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -203,6 +203,9 @@
 			# then chargeable value is "prev invoices + advances" value which cross the threshold
 			tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)
 
+	if cint(tax_details.round_off_tax_amount):
+		tax_amount = round(tax_amount)
+
 	return tax_amount, tax_deducted
 
 def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
@@ -322,9 +325,6 @@
 		else:
 			tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
 
-	if cint(tax_details.round_off_tax_amount):
-		tds_amount = round(tds_amount)
-
 	return tds_amount
 
 def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 5bd6e58..0094bc2 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -421,8 +421,6 @@
 			update_value_in_dict(totals, 'closing', gle)
 
 		elif gle.posting_date <= to_date:
-			update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle)
-			update_value_in_dict(totals, 'total', gle)
 			if filters.get("group_by") != 'Group by Voucher (Consolidated)':
 				gle_map[gle.get(group_by)].entries.append(gle)
 			elif filters.get("group_by") == 'Group by Voucher (Consolidated)':
@@ -436,10 +434,11 @@
 				else:
 					update_value_in_dict(consolidated_gle, key, gle)
 
-			update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle)
-			update_value_in_dict(totals, 'closing', gle)
-
 	for key, value in consolidated_gle.items():
+		update_value_in_dict(gle_map[value.get(group_by)].totals, 'total', value)
+		update_value_in_dict(totals, 'total', value)
+		update_value_in_dict(gle_map[value.get(group_by)].totals, 'closing', value)
+		update_value_in_dict(totals, 'closing', value)
 		entries.append(value)
 
 	return totals, entries
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index e9b531e..88c439b 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1686,17 +1686,58 @@
 
 def update_invoice_status():
 	"""Updates status as Overdue for applicable invoices. Runs daily."""
+	today = getdate()
 
 	for doctype in ("Sales Invoice", "Purchase Invoice"):
 		frappe.db.sql("""
-			update `tab{}` as dt set dt.status = 'Overdue'
-			where dt.docstatus = 1
-				and dt.status != 'Overdue'
-				and dt.outstanding_amount > 0
-				and (dt.grand_total - dt.outstanding_amount) <
-					(select sum(payment_amount) from `tabPayment Schedule` as ps
-						where ps.parent = dt.name and ps.due_date < %s)
-		""".format(doctype), getdate())
+			UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue'
+			WHERE invoice.docstatus = 1
+				AND invoice.status REGEXP '^Unpaid|^Partly Paid'
+				AND invoice.outstanding_amount > 0
+				AND (
+						{or_condition}
+						(
+							(
+								CASE
+									WHEN invoice.party_account_currency = invoice.currency
+									THEN (
+										CASE
+											WHEN invoice.disable_rounded_total
+											THEN invoice.grand_total
+											ELSE invoice.rounded_total
+										END
+									)
+									ELSE (
+										CASE
+											WHEN invoice.disable_rounded_total
+											THEN invoice.base_grand_total
+											ELSE invoice.base_rounded_total
+										END
+									)
+								END
+							) - invoice.outstanding_amount
+						) < (
+							SELECT SUM(
+								CASE
+									WHEN invoice.party_account_currency = invoice.currency
+									THEN ps.payment_amount
+									ELSE ps.base_payment_amount
+								END
+							)
+							FROM `tabPayment Schedule` ps
+							WHERE ps.parent = invoice.name
+								AND ps.due_date < %(today)s
+						)
+					)
+		""".format(
+				doctype=doctype,
+				or_condition=(
+					"invoice.is_pos AND invoice.due_date < %(today)s OR"
+					if doctype == "Sales Invoice"
+					else ""
+				)
+			), {"today": today}
+		)
 
 @frappe.whitelist()
 def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 4697205..08d422d 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -79,8 +79,15 @@
 	def clean_serial_nos(self):
 		for row in self.get("items"):
 			if hasattr(row, "serial_no") and row.serial_no:
-				# replace commas by linefeed and remove all spaces in string
-				row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "")
+				# replace commas by linefeed
+				row.serial_no = row.serial_no.replace(",", "\n")
+
+				# strip preceeding and succeeding spaces for each SN
+				# (SN could have valid spaces in between e.g. SN - 123 - 2021)
+				serial_no_list = row.serial_no.split("\n")
+				serial_no_list = [sn.strip() for sn in serial_no_list]
+
+				row.serial_no = "\n".join(serial_no_list)
 
 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
 			default_cost_center=None):
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index be843a3..55e0efa 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -33,6 +33,7 @@
 		self.validate_item_details()
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_cust_name()
+		self.map_fields()
 
 		if not self.title:
 			self.title = self.customer_name
@@ -43,6 +44,15 @@
 		else:
 			self.calculate_totals()
 
+	def map_fields(self):
+		for field in self.meta.fields:
+			if not self.get(field.fieldname):
+				try:
+					value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname)
+					frappe.db.set(self, field.fieldname, value)
+				except Exception:
+					continue
+
 	def calculate_totals(self):
 		total = base_total = 0
 		for item in self.get('items'):
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 8d6dfa2..8a2da08 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -55,6 +55,7 @@
 			"email": user,
 			"first_name": user,
 			"new_password": "password",
+			"send_welcome_email": 0,
 			"roles": [{"doctype": "Has Role", "role": "Employee"}]
 		}).insert()
 
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 6bca136..d463b9b 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -182,10 +182,11 @@
 	records= frappe.db.sql("""
 		SELECT
 			employee, leave_type, from_date, to_date, leaves, transaction_name,
-			is_carry_forward, is_expired
+			transaction_type, is_carry_forward, is_expired
 		FROM `tabLeave Ledger Entry`
 		WHERE employee=%(employee)s AND leave_type=%(leave_type)s
 			AND docstatus=1
+			AND transaction_type = 'Leave Allocation'
 			AND (from_date between %(from_date)s AND %(to_date)s
 				OR to_date between %(from_date)s AND %(to_date)s
 				OR (from_date < %(from_date)s AND to_date > %(to_date)s))
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index a1df9cf..adb57f9 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -199,12 +199,16 @@
 				if chk:
 					throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order))
 
+	def validate_no_of_visits(self):
+		return len(self.schedules) != sum(d.no_of_visits for d in self.items)
+
 	def validate(self):
 		self.validate_end_date_visits()
 		self.validate_maintenance_detail()
 		self.validate_dates_with_periodicity()
 		self.validate_sales_order()
-		self.generate_schedule()
+		if not self.schedules or self.validate_no_of_visits():
+			self.generate_schedule()
 
 	def on_update(self):
 		frappe.db.set(self, 'status', 'Draft')
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index bd16692..e446d6b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -294,6 +294,7 @@
 erpnext.patches.v13_0.validate_options_for_data_field
 erpnext.patches.v13_0.create_gst_payment_entry_fields
 erpnext.patches.v14_0.delete_shopify_doctypes
+erpnext.patches.v13_0.fix_invoice_statuses
 erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
 erpnext.patches.v13_0.update_dates_in_tax_withholding_category
 erpnext.patches.v14_0.update_opportunity_currency_fields
@@ -307,3 +308,5 @@
 erpnext.patches.v13_0.add_default_interview_notification_templates
 erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting
 erpnext.patches.v13_0.requeue_failed_reposts
+erpnext.patches.v13_0.healthcare_deprecation_warning
+erpnext.patches.v14_0.delete_healthcare_doctypes
diff --git a/erpnext/patches/v13_0/fix_invoice_statuses.py b/erpnext/patches/v13_0/fix_invoice_statuses.py
new file mode 100644
index 0000000..4395757
--- /dev/null
+++ b/erpnext/patches/v13_0/fix_invoice_statuses.py
@@ -0,0 +1,113 @@
+import frappe
+from frappe.utils import flt, getdate
+
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
+	get_total_in_party_account_currency,
+	is_overdue,
+)
+
+TODAY = getdate()
+
+def execute():
+	# This fix is not related to Party Specific Item,
+	# but it is needed for code introduced after Party Specific Item was
+	# If your DB doesn't have this doctype yet, you should be fine
+	if not frappe.db.exists("DocType", "Party Specific Item"):
+		return
+
+	for doctype in ("Purchase Invoice", "Sales Invoice"):
+		fields = [
+			"name",
+			"status",
+			"due_date",
+			"outstanding_amount",
+			"grand_total",
+			"base_grand_total",
+			"rounded_total",
+			"base_rounded_total",
+			"disable_rounded_total",
+		]
+		if doctype == "Sales Invoice":
+			fields.append("is_pos")
+
+		invoices_to_update = frappe.get_all(
+			doctype,
+			fields=fields,
+			filters={
+				"docstatus": 1,
+				"status": ("in", (
+					"Overdue",
+					"Overdue and Discounted",
+					"Partly Paid",
+					"Partly Paid and Discounted"
+				)),
+				"outstanding_amount": (">", 0),
+				"modified": (">", "2021-01-01")
+				# an assumption is being made that only invoices modified
+				# after 2021 got affected as incorrectly overdue.
+				# required for performance reasons.
+			}
+		)
+
+		invoices_to_update = {
+			invoice.name: invoice for invoice in invoices_to_update
+		}
+
+		payment_schedule_items = frappe.get_all(
+			"Payment Schedule",
+			fields=(
+				"due_date",
+				"payment_amount",
+				"base_payment_amount",
+				"parent"
+			),
+			filters={"parent": ("in", invoices_to_update)}
+		)
+
+		for item in payment_schedule_items:
+			invoices_to_update[item.parent].setdefault(
+				"payment_schedule", []
+			).append(item)
+
+		status_map = {}
+
+		for invoice in invoices_to_update.values():
+			invoice.doctype = doctype
+			doc = frappe.get_doc(invoice)
+			correct_status = get_correct_status(doc)
+			if not correct_status or doc.status == correct_status:
+				continue
+
+			status_map.setdefault(correct_status, []).append(doc.name)
+
+		for status, docs in status_map.items():
+			frappe.db.set_value(
+				doctype, {"name": ("in", docs)},
+				"status",
+				status,
+				update_modified=False
+			)
+
+
+
+def get_correct_status(doc):
+	outstanding_amount = flt(
+		doc.outstanding_amount, doc.precision("outstanding_amount")
+	)
+	total = get_total_in_party_account_currency(doc)
+
+	status = ""
+	if is_overdue(doc, total):
+		status = "Overdue"
+	elif 0 < outstanding_amount < total:
+		status = "Partly Paid"
+	elif outstanding_amount > 0 and getdate(doc.due_date) >= TODAY:
+		status = "Unpaid"
+
+	if not status:
+		return
+
+	if doc.status.endswith(" and Discounted"):
+		status += " and Discounted"
+
+	return status
diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
new file mode 100644
index 0000000..28fc01b
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
@@ -0,0 +1,49 @@
+import frappe
+
+
+def execute():
+	if "healthcare" in frappe.get_installed_apps():
+		return
+
+	frappe.delete_doc("Workspace", "Healthcare", ignore_missing=True, force=True)
+
+	pages = frappe.get_all("Page", {"module": "healthcare"}, pluck='name')
+	for page in pages:
+		frappe.delete_doc("Page", page, ignore_missing=True, force=True)
+
+	reports = frappe.get_all("Report", {"module": "healthcare", "is_standard": "Yes"}, pluck='name')
+	for report in reports:
+		frappe.delete_doc("Report", report, ignore_missing=True, force=True)
+
+	print_formats = frappe.get_all("Print Format", {"module": "healthcare", "standard": "Yes"}, pluck='name')
+	for print_format in print_formats:
+		frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
+
+	frappe.reload_doc("website", "doctype", "website_settings")
+	forms = frappe.get_all("Web Form", {"module": "healthcare", "is_standard": 1}, pluck='name')
+	for form in forms:
+		frappe.delete_doc("Web Form", form, ignore_missing=True, force=True)
+
+	dashboards = frappe.get_all("Dashboard", {"module": "healthcare", "is_standard": 1}, pluck='name')
+	for dashboard in dashboards:
+		frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
+
+	dashboards = frappe.get_all("Dashboard Chart", {"module": "healthcare", "is_standard": 1}, pluck='name')
+	for dashboard in dashboards:
+		frappe.delete_doc("Dashboard Chart", dashboard, ignore_missing=True, force=True)
+
+	frappe.reload_doc("desk", "doctype", "number_card")
+	cards = frappe.get_all("Number Card", {"module": "healthcare", "is_standard": 1}, pluck='name')
+	for card in cards:
+		frappe.delete_doc("Number Card", card, ignore_missing=True, force=True)
+
+	titles = ['Lab Test', 'Prescription', 'Patient Appointment']
+	items = frappe.get_all('Portal Menu Item', filters=[['title', 'in', titles]], pluck='name')
+	for item in items:
+		frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
+
+	doctypes = frappe.get_all("DocType", {"module": "healthcare", "custom": 0}, pluck='name')
+	for doctype in doctypes:
+		frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+	frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True)
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 7c0a8ea..b6377f4 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -125,27 +125,28 @@
 		no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
 		return amount_per_day * no_of_days
 
+@frappe.whitelist()
 def get_additional_salaries(employee, start_date, end_date, component_type):
-	additional_salary_list = frappe.db.sql("""
-		select name, salary_component as component, type, amount,
-		overwrite_salary_structure_amount as overwrite,
-		deduct_full_tax_on_selected_payroll_date
-		from `tabAdditional Salary`
-		where employee=%(employee)s
-			and docstatus = 1
-			and (
-					payroll_date between %(from_date)s and %(to_date)s
-				or
-					from_date <= %(to_date)s and to_date >= %(to_date)s
-				)
-		and type = %(component_type)s
-		order by salary_component, overwrite ASC
-	""", {
-		'employee': employee,
-		'from_date': start_date,
-		'to_date': end_date,
-		'component_type': "Earning" if component_type == "earnings" else "Deduction"
-	}, as_dict=1)
+	comp_type = 'Earning' if component_type == 'earnings' else 'Deduction'
+
+	additional_sal = frappe.qb.DocType('Additional Salary')
+	component_field = additional_sal.salary_component.as_('component')
+	overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite')
+
+	additional_salary_list = frappe.qb.from_(
+		additional_sal
+	).select(
+		additional_sal.name, component_field, additional_sal.type,
+		additional_sal.amount, additional_sal.is_recurring, overwrite_field,
+		additional_sal.deduct_full_tax_on_selected_payroll_date
+	).where(
+		(additional_sal.employee == employee)
+		& (additional_sal.docstatus == 1)
+		& (additional_sal.type == comp_type)
+	).where(
+		additional_sal.payroll_date[start_date: end_date]
+		| ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date))
+	).run(as_dict=True)
 
 	additional_salaries = []
 	components_to_overwrite = []
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index 393f647..665f0a8 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -12,6 +12,7 @@
   "year_to_date",
   "section_break_5",
   "additional_salary",
+  "is_recurring_additional_salary",
   "statistical_component",
   "depends_on_payment_days",
   "exempted_from_income_tax",
@@ -235,11 +236,19 @@
    "label": "Year To Date",
    "options": "currency",
    "read_only": 1
-  }
+  },
+  {
+    "default": "0",
+    "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.additional_salary",
+    "fieldname": "is_recurring_additional_salary",
+    "fieldtype": "Check",
+    "label": "Is Recurring Additional Salary",
+    "read_only": 1
+   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-01-14 13:39:15.847158",
+ "modified": "2021-08-30 13:39:15.847158",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index d113e7e..3bc709e 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -630,7 +630,8 @@
 				get_salary_component_data(additional_salary.component),
 				additional_salary.amount,
 				component_type,
-				additional_salary
+				additional_salary,
+				is_recurring = additional_salary.is_recurring
 			)
 
 	def add_tax_components(self, payroll_period):
@@ -651,7 +652,7 @@
 			tax_row = get_salary_component_data(d)
 			self.update_component_row(tax_row, tax_amount, "deductions")
 
-	def update_component_row(self, component_data, amount, component_type, additional_salary=None):
+	def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
 		component_row = None
 		for d in self.get(component_type):
 			if d.salary_component != component_data.salary_component:
@@ -698,6 +699,8 @@
 			else:
 				component_row.default_amount = 0
 				component_row.additional_amount = amount
+
+			component_row.is_recurring_additional_salary = is_recurring
 			component_row.additional_salary = additional_salary.name
 			component_row.deduct_full_tax_on_selected_payroll_date = \
 				additional_salary.deduct_full_tax_on_selected_payroll_date
@@ -894,25 +897,33 @@
 					amount, additional_amount = earning.default_amount, earning.additional_amount
 
 			if earning.is_tax_applicable:
-				if additional_amount:
-					taxable_earnings += (amount - additional_amount)
-					additional_income += additional_amount
-					if earning.deduct_full_tax_on_selected_payroll_date:
-						additional_income_with_full_tax += additional_amount
-					continue
-
 				if earning.is_flexible_benefit:
 					flexi_benefits += amount
 				else:
-					taxable_earnings += amount
+					taxable_earnings += (amount - additional_amount)
+					additional_income += additional_amount
+
+					# Get additional amount based on future recurring additional salary
+					if additional_amount and earning.is_recurring_additional_salary:
+						additional_income += self.get_future_recurring_additional_amount(earning.additional_salary,
+							earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month
+
+					if earning.deduct_full_tax_on_selected_payroll_date:
+						additional_income_with_full_tax += additional_amount
 
 		if allow_tax_exemption:
 			for ded in self.deductions:
 				if ded.exempted_from_income_tax:
-					amount = ded.amount
+					amount, additional_amount = ded.amount, ded.additional_amount
 					if based_on_payment_days:
-						amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0]
-					taxable_earnings -= flt(amount)
+						amount, additional_amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)
+
+					taxable_earnings -= flt(amount - additional_amount)
+					additional_income -= additional_amount
+
+					if additional_amount and ded.is_recurring_additional_salary:
+						additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary,
+							ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month
 
 		return frappe._dict({
 			"taxable_earnings": taxable_earnings,
@@ -921,11 +932,21 @@
 			"flexi_benefits": flexi_benefits
 		})
 
+	def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount):
+		future_recurring_additional_amount = 0
+		to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date')
+		# future month count excluding current
+		future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month)
+		if future_recurring_period > 0:
+			future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month
+		return future_recurring_additional_amount
+
 	def get_amount_based_on_payment_days(self, row, joining_date, relieving_date):
 		amount, additional_amount = row.amount, row.additional_amount
 		if (self.salary_structure and
-			cint(row.depends_on_payment_days) and cint(self.total_working_days) and
-			(not self.salary_slip_based_on_timesheet or
+			cint(row.depends_on_payment_days) and cint(self.total_working_days)
+			and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary
+			and (not self.salary_slip_based_on_timesheet or
 				getdate(self.start_date) < joining_date or
 				(relieving_date and getdate(self.end_date) > relieving_date)
 			)):
@@ -1244,7 +1265,7 @@
 
 		salary_slip_sum = frappe.get_list('Salary Slip',
 			fields = ['sum(net_pay) as net_sum', 'sum(gross_pay) as gross_sum'],
-			filters = {'employee_name' : self.employee_name,
+			filters = {'employee' : self.employee,
 				'start_date' : ['>=', period_start_date],
 				'end_date' : ['<', period_end_date],
 				'name': ['!=', self.name],
@@ -1264,7 +1285,7 @@
 		first_day_of_the_month = get_first_day(self.start_date)
 		salary_slip_sum = frappe.get_list('Salary Slip',
 			fields = ['sum(net_pay) as sum'],
-			filters = {'employee_name' : self.employee_name,
+			filters = {'employee' : self.employee,
 				'start_date' : ['>=', first_day_of_the_month],
 				'end_date' : ['<', self.start_date],
 				'name': ['!=', self.name],
@@ -1288,13 +1309,13 @@
 					INNER JOIN `tabSalary Slip` as salary_slip
 					ON detail.parent = salary_slip.name
 					WHERE
-						salary_slip.employee_name = %(employee_name)s
+						salary_slip.employee = %(employee)s
 						AND detail.salary_component = %(component)s
 						AND salary_slip.start_date >= %(period_start_date)s
 						AND salary_slip.end_date < %(period_end_date)s
 						AND salary_slip.name != %(docname)s
 						AND salary_slip.docstatus = 1""",
-						{'employee_name': self.employee_name, 'component': component.salary_component, 'period_start_date': period_start_date,
+						{'employee': self.employee, 'component': component.salary_component, 'period_start_date': period_start_date,
 							'period_end_date': period_end_date, 'docname': self.name}
 				)
 
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 178cd5c..c4b6a38 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -536,6 +536,61 @@
 		# undelete fixture data
 		frappe.db.rollback()
 
+	def test_tax_for_recurring_additional_salary(self):
+		frappe.db.sql("""delete from `tabPayroll Period`""")
+		frappe.db.sql("""delete from `tabSalary Component`""")
+
+		payroll_period = create_payroll_period()
+
+		create_tax_slab(payroll_period, allow_tax_exemption=True)
+
+		employee = make_employee("test_tax@salary.slip")
+		delete_docs = [
+			"Salary Slip",
+			"Additional Salary",
+			"Employee Tax Exemption Declaration",
+			"Employee Tax Exemption Proof Submission",
+			"Employee Benefit Claim",
+			"Salary Structure Assignment"
+		]
+		for doc in delete_docs:
+			frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee))
+
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+
+		salary_structure = make_salary_structure("Stucture to test tax", "Monthly",
+			other_details={"max_benefits": 100000}, test_tax=True,
+			employee=employee, payroll_period=payroll_period)
+
+
+		create_salary_slips_for_payroll_period(employee, salary_structure.name,
+			payroll_period, deduct_random=False, num=3)
+
+		tax_paid = get_tax_paid_in_period(employee)
+
+		annual_tax = 23196.0
+		self.assertEqual(tax_paid, annual_tax)
+
+		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
+
+		#------------------------------------
+		# Recurring additional salary
+		start_date = add_months(payroll_period.start_date, 3)
+		end_date = add_months(payroll_period.start_date, 5)
+		create_recurring_additional_salary(employee, "Performance Bonus", 20000, start_date, end_date)
+
+		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee))
+
+		create_salary_slips_for_payroll_period(employee, salary_structure.name,
+			payroll_period, deduct_random=False, num=4)
+
+		tax_paid = get_tax_paid_in_period(employee)
+
+		annual_tax = 32315.0
+		self.assertEqual(tax_paid, annual_tax)
+
+		frappe.db.rollback()
+
 	def make_activity_for_employee(self):
 		activity_type = frappe.get_doc("Activity Type", "_Test Activity Type")
 		activity_type.billing_rate = 50
@@ -1007,3 +1062,17 @@
 		salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)
 
 	return salary_slip
+
+def create_recurring_additional_salary(employee, salary_component, amount, from_date, to_date, company=None):
+	frappe.get_doc({
+		"doctype": "Additional Salary",
+		"employee": employee,
+		"company": company or erpnext.get_default_company(),
+		"salary_component": salary_component,
+		"is_recurring": 1,
+		"from_date": from_date,
+		"to_date": to_date,
+		"amount": amount,
+		"type": "Earning",
+		"currency": erpnext.get_default_currency()
+	}).submit()
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index 1655b76..65a8566 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -32,12 +32,12 @@
 		};
 	},
 
-	onload: function(frm){
+	onload: function(frm) {
 		if (frm.doc.__islocal && frm.doc.time_logs) {
 			calculate_time_and_amount(frm);
 		}
 
-		if (frm.is_new()) {
+		if (frm.is_new() && !frm.doc.employee) {
 			set_employee_and_company(frm);
 		}
 	},
diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
index 7b35819..831626a 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
@@ -334,10 +334,12 @@
 
 			if (child_nodes) {
 				$.each(child_nodes, (_i, data) => {
-					this.add_node(node, data);
-					setTimeout(() => {
-						this.add_connector(node.id, data.id);
-					}, 250);
+					if (!$(`[id="${data.id}"]`).length) {
+						this.add_node(node, data);
+						setTimeout(() => {
+							this.add_connector(node.id, data.id);
+						}, 250);
+					}
 				});
 			}
 		}
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 23924c5..7d401ba 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -172,13 +172,6 @@
 		self.invoices = frappe._dict()
 		conditions = self.get_conditions()
 
-		company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True)
-
-		if company_gstins:
-			self.filters.update({
-				'company_gstins': company_gstins
-			})
-
 		invoice_data = frappe.db.sql("""
 			select
 				{select_columns}
@@ -242,7 +235,7 @@
 		elif self.filters.get("type_of_business") ==  "EXPORT":
 			conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
 
-		conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s"
+		conditions += " AND IFNULL(billing_address_gstin, '') != company_gstin"
 
 		return conditions
 
diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json
index 3a4e7f6..844a11f 100644
--- a/erpnext/setup/doctype/uom/uom.json
+++ b/erpnext/setup/doctype/uom/uom.json
@@ -1,164 +1,82 @@
 {
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:uom_name",
- "beta": 0,
  "creation": "2013-01-10 16:34:24",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
  "document_type": "Setup",
- "editable_grid": 0,
+ "engine": "InnoDB",
+ "field_order": [
+  "enabled",
+  "uom_name",
+  "must_be_whole_number"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "uom_name",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "UOM Name",
-   "length": 0,
-   "no_copy": 0,
    "oldfieldname": "uom_name",
    "oldfieldtype": "Data",
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
    "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
    "unique": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
+   "default": "0",
    "description": "Check this to disallow fractions. (for Nos)",
    "fieldname": "must_be_whole_number",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Must be Whole Number",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Must be Whole Number"
+  },
+  {
+   "default": "1",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "label": "Enabled"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
  "icon": "fa fa-compass",
  "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-29 06:35:56.143361",
+ "links": [],
+ "modified": "2021-10-18 14:07:43.722144",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "UOM",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
    "import": 1,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "Item Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
    "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
-   "role": "Stock Manager",
-   "set_user_permissions": 0,
-   "share": 0,
-   "submit": 0,
-   "write": 0
+   "role": "Stock Manager"
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
    "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
-   "role": "Stock User",
-   "set_user_permissions": 0,
-   "share": 0,
-   "submit": 0,
-   "write": 0
+   "role": "Stock User"
   }
  ],
  "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
  "show_name_in_global_search": 1,
- "sort_order": "ASC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "sort_field": "modified",
+ "sort_order": "ASC"
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index 546e21b..570f22e 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -184,14 +184,14 @@
 
 		se = frappe.copy_doc(test_records[0])
 		se.get("items")[0].item_code = item_code
-		se.get("items")[0].qty = 3
-		se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3  "
-		se.get("items")[0].transfer_qty = 3
+		se.get("items")[0].qty = 4
+		se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3  , _TS4 - 2021"
+		se.get("items")[0].transfer_qty = 4
 		se.set_stock_entry_type()
 		se.insert()
 		se.submit()
 
-		self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3")
+		self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021")
 
 		frappe.db.rollback()
 
diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json
index 5d3d3ac..bf1daa1 100644
--- a/erpnext/support/doctype/support_settings/support_settings.json
+++ b/erpnext/support/doctype/support_settings/support_settings.json
@@ -37,7 +37,6 @@
   },
   {
    "default": "7",
-   "description": "Auto close Issue after 7 days",
    "fieldname": "close_issue_after_days",
    "fieldtype": "Int",
    "label": "Close Issue After Days"
@@ -164,7 +163,7 @@
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-06-11 13:08:38.473616",
+ "modified": "2021-10-14 13:08:38.473616",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Support Settings",
@@ -185,4 +184,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}