Merge pull request #27749 from GangaManoj/fix-pe-payment-term

fix: Display appropriate message for Payment Term discrepancies in Payment Entry
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 f67c59c..66a269e 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
@@ -12,11 +12,6 @@
 		frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1);
 		frm.set_df_property('chart_preview', 'hidden',
 			$(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1);
-
-		// Show import button when file is successfully attached
-		if (frm.page && frm.page.show_import_button) {
-			create_import_button(frm);
-		}
 	},
 
 	download_template: function(frm) {
@@ -78,7 +73,12 @@
 			frm.page.set_indicator("");
 			$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
 		} else {
-			generate_tree_preview(frm);
+			frappe.run_serially([
+				() => validate_coa(frm),
+				() => generate_tree_preview(frm),
+				() => create_import_button(frm),
+				() => frm.set_df_property('chart_preview', 'hidden', 0),
+			]);
 		}
 	},
 
@@ -104,24 +104,26 @@
 });
 
 var create_import_button = function(frm) {
-	frm.page.set_primary_action(__("Import"), function () {
-		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);
+	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);
+					}
 				}
-			}
-		});
-	}).addClass('btn btn-primary');
+			});
+		}).addClass('btn btn-primary');
+	}
 };
 
 var create_reset_button = function(frm) {
@@ -132,13 +134,35 @@
 	}).addClass('btn btn-primary');
 };
 
+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': {
+				file_name: frm.doc.import_file,
+				parent: parent,
+				doctype: 'Chart of Accounts Importer',
+				file_type: frm.doc.file_type,
+				for_validate: 1
+			},
+			callback: function(r) {
+				if (r.message['show_import_button']) {
+					frm.page['show_import_button'] = Boolean(r.message['show_import_button']);
+				}
+			}
+		});
+	}
+};
+
 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
 
 		// generate tree structure based on the csv data
-		new frappe.ui.Tree({
+		return new frappe.ui.Tree({
 			parent: $(frm.fields_dict['chart_tree'].wrapper),
 			label: parent,
 			expandable: true,
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 9a0234a..bd2a6f1 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
@@ -64,6 +64,7 @@
 	else:
 		data = generate_data_from_excel(file_doc, extension)
 
+	frappe.local.flags.ignore_root_company_validation = True
 	forest = build_forest(data)
 	create_charts(company, custom_chart=forest)
 
@@ -128,7 +129,7 @@
 	return data
 
 @frappe.whitelist()
-def get_coa(doctype, parent, is_root=False, file_name=None):
+def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
 	''' called by tree view (to fetch node's children) '''
 
 	file_doc, extension = get_file(file_name)
@@ -140,14 +141,20 @@
 		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
+	validate_accounts(file_doc, extension)
 
-	# filter out to show data for the selected node only
-	accounts = [d for d in accounts if d['parent_account']==parent]
+	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
 
-	return accounts
+		# filter out to show data for the selected node only
+		accounts = [d for d in accounts if d['parent_account']==parent]
+
+		return accounts
+	else:
+		return {
+			'show_import_button': 1
+		}
 
 def build_forest(data):
 	'''
@@ -304,10 +311,7 @@
 
 
 @frappe.whitelist()
-def validate_accounts(file_name):
-
-	file_doc, extension = get_file(file_name)
-
+def validate_accounts(file_doc, extension):
 	if extension  == 'csv':
 		accounts = generate_data_from_csv(file_doc, as_dict=True)
 	else:
@@ -326,8 +330,6 @@
 
 	validate_root(accounts_dict)
 
-	validate_account_types(accounts_dict)
-
 	return [True, len(accounts)]
 
 def validate_root(accounts):
@@ -340,9 +342,19 @@
 		elif account.get("root_type") not in get_root_types() and account.get("account_name"):
 			error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
 
+	validate_missing_roots(roots)
+
 	if error_messages:
 		frappe.throw("<br>".join(error_messages))
 
+def validate_missing_roots(roots):
+	root_types_added = set(d.get('root_type') for d in roots)
+
+	missing = list(set(get_root_types()) - root_types_added)
+
+	if missing:
+		frappe.throw(_("Please add Root Account for - {0}").format(' , '.join(missing)))
+
 def get_root_types():
 	return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
 
@@ -368,15 +380,6 @@
 		{'account_type': 'Stock', 'root_type': 'Asset'}
 	]
 
-
-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 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)))
-
 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/patches.txt b/erpnext/patches.txt
index 3d5ecbd..5dfd6be 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -309,6 +309,7 @@
 erpnext.patches.v14_0.update_opportunity_currency_fields
 erpnext.patches.v13_0.gst_fields_for_pos_invoice
 erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
+erpnext.patches.v13_0.trim_sales_invoice_custom_field_length
 erpnext.patches.v13_0.create_custom_field_for_finance_book
 erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries
 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry
diff --git a/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py
new file mode 100644
index 0000000..fd48c0d
--- /dev/null
+++ b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+from erpnext.regional.india.setup import create_custom_fields, get_custom_fields
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Sales Invoice': get_custom_fields().get('Sales Invoice')
+	}
+
+	create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 4a2c577..afb1b07 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -132,6 +132,10 @@
 		make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
 
 def make_custom_fields(update=True):
+	custom_fields = get_custom_fields()
+	create_custom_fields(custom_fields, update=update)
+
+def get_custom_fields():
 	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
 		fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
 		allow_on_submit=1, print_hide=1, fetch_if_empty=1)
@@ -165,12 +169,12 @@
 		dict(fieldname='gst_category', label='GST Category',
 			fieldtype='Select', insert_after='gst_section', print_hide=1,
 			options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
-			fetch_from='customer.gst_category', fetch_if_empty=1),
+			fetch_from='customer.gst_category', fetch_if_empty=1, length=25),
 		dict(fieldname='export_type', label='Export Type',
 			fieldtype='Select', insert_after='gst_category', print_hide=1,
 			depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
 			options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='customer.export_type',
-			fetch_if_empty=1),
+			fetch_if_empty=1, length=25),
 	]
 
 	delivery_note_gst_category = [
@@ -181,18 +185,18 @@
 	]
 
 	invoice_gst_fields = [
-		dict(fieldname='invoice_copy', label='Invoice Copy',
+		dict(fieldname='invoice_copy', label='Invoice Copy', length=30,
 			fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1,
 			options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'),
-		dict(fieldname='reverse_charge', label='Reverse Charge',
+		dict(fieldname='reverse_charge', label='Reverse Charge', length=2,
 			fieldtype='Select', insert_after='invoice_copy', print_hide=1,
 			options='Y\nN', default='N'),
-		dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN',
+		dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', length=15,
 			fieldtype='Data', insert_after='export_type', print_hide=1),
 		dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='ecommerce_gstin'),
 		dict(fieldname='reason_for_issuing_document', label='Reason For Issuing document',
 			fieldtype='Select', insert_after='gst_col_break', print_hide=1,
-			depends_on='eval:doc.is_return==1',
+			depends_on='eval:doc.is_return==1', length=45,
 			options='\n01-Sales Return\n02-Post Sale Discount\n03-Deficiency in services\n04-Correction in Invoice\n05-Change in POS\n06-Finalization of Provisional assessment\n07-Others')
 	]
 
@@ -230,25 +234,25 @@
 	sales_invoice_gst_fields = [
 			dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
 				fieldtype='Data', insert_after='customer_address', read_only=1,
-				fetch_from='customer_address.gstin', print_hide=1),
+				fetch_from='customer_address.gstin', print_hide=1, length=15),
 			dict(fieldname='customer_gstin', label='Customer GSTIN',
 				fieldtype='Data', insert_after='shipping_address_name',
-				fetch_from='shipping_address_name.gstin', print_hide=1),
+				fetch_from='shipping_address_name.gstin', print_hide=1, length=15),
 			dict(fieldname='place_of_supply', label='Place of Supply',
 				fieldtype='Data', insert_after='customer_gstin',
-				print_hide=1, read_only=1),
+				print_hide=1, read_only=1, length=50),
 			dict(fieldname='company_gstin', label='Company GSTIN',
 				fieldtype='Data', insert_after='company_address',
-				fetch_from='company_address.gstin', print_hide=1, read_only=1),
+				fetch_from='company_address.gstin', print_hide=1, read_only=1, length=15),
 		]
 
 	sales_invoice_shipping_fields = [
 			dict(fieldname='port_code', label='Port Code',
 				fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1,
-				depends_on="eval:doc.gst_category=='Overseas' "),
+				depends_on="eval:doc.gst_category=='Overseas' ", length=15),
 			dict(fieldname='shipping_bill_number', label=' Shipping Bill Number',
 				fieldtype='Data', insert_after='port_code', print_hide=1,
-				depends_on="eval:doc.gst_category=='Overseas' "),
+				depends_on="eval:doc.gst_category=='Overseas' ", length=50),
 			dict(fieldname='shipping_bill_date', label='Shipping Bill Date',
 				fieldtype='Date', insert_after='shipping_bill_number', print_hide=1,
 				depends_on="eval:doc.gst_category=='Overseas' "),
@@ -354,7 +358,8 @@
 			'insert_after': 'transporter',
 			'fetch_from': 'transporter.gst_transporter_id',
 			'print_hide': 1,
-			'translatable': 0
+			'translatable': 0,
+			'length': 20
 		},
 		{
 			'fieldname': 'driver',
@@ -370,7 +375,8 @@
 			'fieldtype': 'Data',
 			'insert_after': 'driver',
 			'print_hide': 1,
-			'translatable': 0
+			'translatable': 0,
+			'length': 30
 		},
 		{
 			'fieldname': 'vehicle_no',
@@ -378,7 +384,8 @@
 			'fieldtype': 'Data',
 			'insert_after': 'lr_no',
 			'print_hide': 1,
-			'translatable': 0
+			'translatable': 0,
+			'length': 10
 		},
 		{
 			'fieldname': 'distance',
@@ -395,7 +402,7 @@
 		{
 			'fieldname': 'transporter_name',
 			'label': 'Transporter Name',
-			'fieldtype': 'Data',
+			'fieldtype': 'Small Text',
 			'insert_after': 'transporter_col_break',
 			'fetch_from': 'transporter.name',
 			'read_only': 1,
@@ -409,12 +416,13 @@
 			'options': '\nRoad\nAir\nRail\nShip',
 			'insert_after': 'transporter_name',
 			'print_hide': 1,
-			'translatable': 0
+			'translatable': 0,
+			'length': 5
 		},
 		{
 			'fieldname': 'driver_name',
 			'label': 'Driver Name',
-			'fieldtype': 'Data',
+			'fieldtype': 'Small Text',
 			'insert_after': 'mode_of_transport',
 			'fetch_from': 'driver.full_name',
 			'print_hide': 1,
@@ -437,7 +445,8 @@
 			'default': 'Regular',
 			'insert_after': 'lr_date',
 			'print_hide': 1,
-			'translatable': 0
+			'translatable': 0,
+			'length': 30
 		},
 		{
 			'fieldname': 'ewaybill',
@@ -446,7 +455,8 @@
 			'depends_on': 'eval:(doc.docstatus === 1)',
 			'allow_on_submit': 1,
 			'insert_after': 'tax_id',
-			'translatable': 0
+			'translatable': 0,
+			'length': 20
 		}
 	]
 
@@ -674,7 +684,8 @@
 			}
 		]
 	}
-	create_custom_fields(custom_fields, update=update)
+
+	return custom_fields
 
 def make_fixtures(company=None):
 	docs = []
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index 0faf80b..9493614 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -249,6 +249,9 @@
 	elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
 		destination_gstin = party_details.supplier_gstin
 
+	if not destination_gstin or party_details.gstin:
+		return False
+
 	if party_details.gstin == destination_gstin:
 		return True
 	else:
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
index b649b87..5f470aa 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json
@@ -203,10 +203,11 @@
   }
  ],
  "links": [],
- "modified": "2021-07-27 11:16:45.596579",
+ "modified": "2021-10-02 11:32:55.556024",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Service Level Agreement",
+ "naming_rule": "Expression",
  "owner": "Administrator",
  "permissions": [
   {
@@ -237,4 +238,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index bd47811..6bdd8f2 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -339,7 +339,7 @@
 
 def apply(doc, method=None):
 	# Applies SLA to document on validate
-	if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \
+	if frappe.flags.in_patch or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_setup_wizard or \
 		doc.doctype not in get_documents_with_active_service_level_agreement():
 		return