Merge branch 'develop' into fix-depr-after-sale
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 f795dfa..f67c59c 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
@@ -79,7 +79,6 @@
 			$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
 		} else {
 			generate_tree_preview(frm);
-			validate_csv_data(frm);
 		}
 	},
 
@@ -104,23 +103,6 @@
 	}
 });
 
-var validate_csv_data = function(frm) {
-	frappe.call({
-		method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts",
-		args: {file_name: frm.doc.import_file},
-		callback: function(r) {
-			if(r.message && r.message[0]===true) {
-				frm.page["show_import_button"] = true;
-				frm.page["total_accounts"] = r.message[1];
-				frm.trigger("refresh");
-			} else {
-				frm.page.set_indicator(__('Resolve error and upload again.'), 'orange');
-				frappe.throw(__(r.message));
-			}
-		}
-	});
-};
-
 var create_import_button = function(frm) {
 	frm.page.set_primary_action(__("Import"), function () {
 		frappe.call({
@@ -151,23 +133,25 @@
 };
 
 var generate_tree_preview = function(frm) {
-	let parent = __('All Accounts');
-	$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
+	if (frm.doc.import_file) {
+		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
-	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
+		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 61968cf..9a0234a 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,8 +25,16 @@
 
 
 class ChartofAccountsImporter(Document):
-	def validate(self):
-		validate_accounts(self.import_file)
+	pass
+
+def validate_columns(data):
+	if not data:
+		frappe.throw(_('No data found. Seems like you uploaded a blank file'))
+
+	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.whitelist()
 def validate_company(company):
@@ -131,6 +139,8 @@
 	else:
 		data = generate_data_from_excel(file_doc, extension)
 
+	validate_columns(data)
+	validate_accounts(data)
 	forest = build_forest(data)
 	accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
 
@@ -322,9 +332,6 @@
 
 def validate_root(accounts):
 	roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
-	if len(roots) < 4:
-		frappe.throw(_("Number of root accounts cannot be less than 4"))
-
 	error_messages = []
 
 	for account in roots:
@@ -364,20 +371,12 @@
 
 def validate_account_types(accounts):
 	account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
-	account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1]
+	account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1]
 
 	missing = list(set(account_types_for_ledger) - set(account_types))
 	if missing:
 		frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
 
-	account_types_for_group = ["Bank", "Cash", "Stock"]
-	# fix logic bug
-	account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1]
-
-	missing = list(set(account_types_for_group) - set(account_groups))
-	if missing:
-		frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
-
 def unset_existing_data(company):
 	linked = frappe.db.sql('''select fieldname from tabDocField
 		where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True)
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
index 3653501..b8c65ee 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -7,6 +7,7 @@
  "field_order": [
   "reference_type",
   "reference_name",
+  "reference_row",
   "column_break_3",
   "invoice_type",
   "invoice_number",
@@ -121,11 +122,17 @@
    "label": "Amount",
    "options": "Currency",
    "read_only": 1
+  },
+  {
+   "fieldname": "reference_row",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Reference Row"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-08-30 10:58:42.665107",
+ "modified": "2021-09-20 17:23:09.455803",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation Allocation",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index b90db05..5359089 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -980,42 +980,55 @@
 		item_allowance = {}
 		global_qty_allowance, global_amount_allowance = None, None
 
+		role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+		user_roles = frappe.get_roles()
+
+		total_overbilled_amt = 0.0
+
 		for item in self.get("items"):
-			if item.get(item_ref_dn):
-				ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
-					item.get(item_ref_dn), based_on), self.precision(based_on, item))
-				if not ref_amt:
-					frappe.msgprint(
-						_("Warning: System will not check overbilling since amount for Item {0} in {1} is zero")
-							.format(item.item_code, ref_dt))
-				else:
-					already_billed = frappe.db.sql("""
-						select sum(%s)
-						from `tab%s`
-						where %s=%s and docstatus=1 and parent != %s
-					""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
-					   (item.get(item_ref_dn), self.name))[0][0]
+			if not item.get(item_ref_dn):
+				continue
 
-					total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
-						self.precision(based_on, item))
+			ref_amt = flt(frappe.db.get_value(ref_dt + " Item",
+				item.get(item_ref_dn), based_on), self.precision(based_on, item))
+			if not ref_amt:
+				frappe.msgprint(
+					_("System will not check overbilling since amount for Item {0} in {1} is zero")
+						.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
+				continue
 
-					allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
-						get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
+			already_billed = frappe.db.sql("""
+				select sum(%s)
+				from `tab%s`
+				where %s=%s and docstatus=1 and parent != %s
+			""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
+				(item.get(item_ref_dn), self.name))[0][0]
 
-					max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
+			total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
+				self.precision(based_on, item))
 
-					if total_billed_amt < 0 and max_allowed_amt < 0:
-						# while making debit note against purchase return entry(purchase receipt) getting overbill error
-						total_billed_amt = abs(total_billed_amt)
-						max_allowed_amt = abs(max_allowed_amt)
+			allowance, item_allowance, global_qty_allowance, global_amount_allowance = \
+				get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount")
 
-					role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
+			max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
 
-					if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
-						if self.doctype != "Purchase Invoice":
-							self.throw_overbill_exception(item, max_allowed_amt)
-						elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
-							self.throw_overbill_exception(item, max_allowed_amt)
+			if total_billed_amt < 0 and max_allowed_amt < 0:
+				# while making debit note against purchase return entry(purchase receipt) getting overbill error
+				total_billed_amt = abs(total_billed_amt)
+				max_allowed_amt = abs(max_allowed_amt)
+
+			overbill_amt = total_billed_amt - max_allowed_amt
+			total_overbilled_amt += overbill_amt
+
+			if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
+				if self.doctype != "Purchase Invoice":
+					self.throw_overbill_exception(item, max_allowed_amt)
+				elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")):
+					self.throw_overbill_exception(item, max_allowed_amt)
+
+		if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
+			frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
+					.format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange")
 
 	def throw_overbill_exception(self, item, max_allowed_amt):
 		frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index d2748c2..310afed 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -85,10 +85,8 @@
 		if not acc_subtype:
 			add_account_subtype(account["subtype"])
 
-		existing_bank_account = frappe.db.exists("Bank Account", {
-			'account_name': account["name"],
-			'bank': bank["bank_name"]
-		})
+		bank_account_name = "{} - {}".format(account["name"], bank["bank_name"])
+		existing_bank_account = frappe.db.exists("Bank Account", bank_account_name)
 
 		if not existing_bank_account:
 			try:
@@ -197,6 +195,7 @@
 
 	plaid = PlaidConnector(access_token)
 
+	transactions = []
 	try:
 		transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
 	except ItemError as e:
@@ -205,7 +204,7 @@
 			msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
 			frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
 
-	return transactions or []
+	return transactions
 
 
 def new_bank_transaction(transaction):
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index bfccb48..01742d1 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -112,10 +112,7 @@
 		frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
 
 def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
-	if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
-		return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts
-	else:
-		return [_("Item"), _("Taxable Amount")] + tax_accounts
+	return [_("Item"), _("Taxable Amount")] + tax_accounts
 
 def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False):
 	itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise)
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index d899c5c..ec861d7 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -236,7 +236,7 @@
 				if (this.value) {
 					me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
 						me.item_stock_map = me.events.get_item_stock_map();
-						const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
+						const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value];
 						if (available_qty === undefined) {
 							me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
 								// item stock map is updated now reset warehouse