feat: Chart of Accounts Importer (#16623)
* bare doctype created for COA utility
* improvise doctype's design
* validation to check no transaction exist for the company
* download file and import coa - client side logic added
* download csv template to create custom chart
* read the custom csv uploaded and parse it to appropriate format
* convert list of list to nested tree structure
* preview the uploaded chart before actual import
* toggle field based on necessity
* tweak create_charts and build_json logic to incorporate COA Import utility
* code cleanify and validation call added
* code enhancement for flexibility and validation added
* show import button only if data is validated
* unset existing data and load new accounts
* disable coa fields if parent_company set, minor improv
* file api fix
* added progress bar
* codacy fixes
* fix: Add account number in template
* fix: TDS account exception handling fix
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 bcb163f..1bf9196 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
@@ -9,8 +9,8 @@
from six import iteritems
from frappe.utils.nestedset import rebuild_tree
-def create_charts(company, chart_template=None, existing_company=None):
- chart = get_chart(chart_template, existing_company)
+def create_charts(company, chart_template=None, existing_company=None, custom_chart=None):
+ chart = custom_chart or get_chart(chart_template, existing_company)
if chart:
accounts = []
@@ -40,7 +40,7 @@
"report_type": report_type,
"account_number": account_number,
"account_type": child.get("account_type"),
- "account_currency": frappe.db.get_value('Company', company, "default_currency"),
+ "account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
"tax_rate": child.get("tax_rate")
})
@@ -207,9 +207,9 @@
return (bank_account in accounts)
@frappe.whitelist()
-def build_tree_from_json(chart_template):
+def build_tree_from_json(chart_template, chart_data=None):
''' get chart template from its folder and parse the json to be rendered as tree '''
- chart = get_chart(chart_template)
+ chart = chart_data or get_chart(chart_template)
# if no template selected, return as it is
if not chart:
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/__init__.py b/erpnext/accounts/doctype/chart_of_accounts_importer/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/__init__.py
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
new file mode 100644
index 0000000..aea6080
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js
@@ -0,0 +1,138 @@
+frappe.ui.form.on('Chart of Accounts Importer', {
+ onload: function (frm) {
+ frm.set_value("company", "");
+ frm.set_value("import_file", "");
+ },
+ refresh: function (frm) {
+ // disable default save
+ frm.disable_save();
+
+ // 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);
+ 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);
+ }
+
+ // show download template button when company is properly selected
+ if(frm.doc.company) {
+ // download the csv template file
+ frm.add_custom_button(__("Download template"), function () {
+ let get_template_url = 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template';
+ open_url_post(frappe.request.url, { cmd: get_template_url, doctype: frm.doc.doctype });
+ });
+ } else {
+ frm.set_value("import_file", "");
+ }
+ },
+
+ import_file: function (frm) {
+ if (!frm.doc.import_file) {
+ frm.page.set_indicator("");
+ $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
+ } else {
+ generate_tree_preview(frm);
+ validate_csv_data(frm);
+ }
+ },
+
+ company: function (frm) {
+ // validate that no Gl Entry record for the company exists.
+ frappe.call({
+ method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
+ args: {
+ company: frm.doc.company
+ },
+ callback: function(r) {
+ if(r.message===false) {
+ frm.set_value("company", "");
+ frappe.throw(__("Transactions against the company already exist! "));
+ } else {
+ frm.trigger("refresh");
+ }
+ }
+ });
+ }
+});
+
+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(__("Start Import"), function () {
+ setup_progress_bar(frm);
+ 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,
+ callback: function(r) {
+ if(!r.exc) {
+ clearInterval(frm.page["interval"]);
+ frm.page.set_indicator(__('Import Successfull'), 'blue');
+ frappe.hide_progress();
+ create_reset_button(frm);
+ }
+ }
+ });
+ }).addClass('btn btn-primary');
+};
+
+var create_reset_button = function(frm) {
+ frm.page.set_primary_action(__("Reset"), function () {
+ frm.page.clear_primary_action();
+ delete frm.page["show_import_button"];
+ frm.reload_doc();
+ }).addClass('btn btn-primary');
+};
+
+var generate_tree_preview = function(frm) {
+ 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'
+ },
+ onclick: function(node) {
+ parent = node.value;
+ }
+ });
+};
+
+var setup_progress_bar = function(frm) {
+ frm.page["seconds_elapsed"] = 0;
+ frm.page["execution_time"] = (frm.page["total_accounts"] > 100) ? 100 : frm.page["total_accounts"];
+
+ frm.page["interval"] = setInterval(function() {
+ frm.page["seconds_elapsed"] += 1;
+ frappe.show_progress(__('Creating Accounts'), frm.page["seconds_elapsed"], frm.page["execution_time"]);
+ }, 250);
+};
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json
new file mode 100644
index 0000000..d544e69
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json
@@ -0,0 +1,226 @@
+{
+ "allow_copy": 1,
+ "allow_events_in_timeline": 0,
+ "allow_guest_to_view": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2019-02-01 12:24:34.761380",
+ "custom": 0,
+ "description": "Import Chart of Accounts from a csv file",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Other",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "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": "Company",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Company",
+ "permlevel": 0,
+ "precision": "",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "import_file_section",
+ "fieldtype": "Section Break",
+ "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,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "depends_on": "",
+ "fieldname": "import_file",
+ "fieldtype": "Attach",
+ "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": "Attach custom Chart of Accounts file",
+ "length": 0,
+ "no_copy": 0,
+ "options": "",
+ "permlevel": 0,
+ "precision": "",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "columns": 0,
+ "fieldname": "chart_preview",
+ "fieldtype": "Section Break",
+ "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": "Chart Preview",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "chart_tree",
+ "fieldtype": "HTML",
+ "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": "Chart Tree",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "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
+ }
+ ],
+ "has_web_view": 0,
+ "hide_heading": 1,
+ "hide_toolbar": 1,
+ "idx": 0,
+ "image_view": 0,
+ "in_create": 1,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2019-02-04 23:10:30.136807",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Chart of Accounts Importer",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 0,
+ "email": 0,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "Accounts Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 1,
+ "read_only_onload": 0,
+ "show_name_in_global_search": 0,
+ "sort_field": "",
+ "sort_order": "DESC",
+ "track_changes": 0,
+ "track_seen": 0,
+ "track_views": 0
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..948be64
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe, csv
+from frappe import _
+from frappe.utils import cstr
+from frappe.model.document import Document
+from frappe.utils.csvutils import UnicodeWriter
+from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts, build_tree_from_json
+
+class ChartofAccountsImporter(Document):
+ pass
+
+@frappe.whitelist()
+def validate_company(company):
+ if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
+ return False
+
+@frappe.whitelist()
+def import_coa(file_name, company):
+ # delete existing data for accounts
+ unset_existing_data(company)
+
+ # create accounts
+ forest = build_forest(generate_data_from_csv(file_name))
+ create_charts(company, custom_chart=forest)
+
+ # trigger on_update for company to reset default accounts
+ set_default_accounts(company)
+
+def generate_data_from_csv(file_name, as_dict=False):
+ ''' read csv file and return the generated nested tree '''
+ file_doc = frappe.get_doc('File', {"file_url": file_name})
+ file_path = file_doc.get_full_path()
+
+ data = []
+ with open(file_path, 'r') as in_file:
+ csv_reader = list(csv.reader(in_file))
+ headers = csv_reader[1][1:]
+ del csv_reader[0:2] # delete top row and headers row
+
+ for row in csv_reader:
+ if as_dict:
+ data.append({frappe.scrub(header): row[index+1] for index, header in enumerate(headers)})
+ else:
+ if not row[2]: row[2] = row[1]
+ data.append(row[1:])
+
+ # convert csv data
+ return data
+
+@frappe.whitelist()
+def get_coa(doctype, parent, is_root=False, file_name=None):
+ ''' called by tree view (to fetch node's children) '''
+
+ parent = None if parent==_('All Accounts') else parent
+ forest = build_forest(generate_data_from_csv(file_name))
+ accounts = build_tree_from_json("", chart_data=forest) # returns alist 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]
+
+ return accounts
+
+def build_forest(data):
+ '''
+ converts list of list into a nested tree
+ if a = [[1,1], [1,2], [3,2], [4,4], [5,4]]
+ tree = {
+ 1: {
+ 2: {
+ 3: {}
+ }
+ },
+ 4: {
+ 5: {}
+ }
+ }
+ '''
+
+ # set the value of nested dictionary
+ def set_nested(d, path, value):
+ reduce(lambda d, k: d.setdefault(k, {}), path[:-1], d)[path[-1]] = value
+ return d
+
+ # returns the path of any node in list format
+ def return_parent(data, child):
+ for row in data:
+ account_name, parent_account = row[0:2]
+ if parent_account == account_name == child:
+ return [parent_account]
+ elif account_name == child:
+ return [child] + return_parent(data, parent_account)
+
+ charts_map, paths = {}, []
+ for i in data:
+ account_name, _, account_number, is_group, account_type, root_type = i
+ charts_map[account_name] = {}
+ if is_group: 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
+ if account_number: charts_map[account_name]["account_number"] = account_number
+ path = return_parent(data, account_name)[::-1]
+ paths.append(path) # List of path is created
+
+ out = {}
+ for path in paths:
+ for n, account_name in enumerate(path):
+ set_nested(out, path[:n+1], charts_map[account_name]) # setting the value of nested dictionary.
+
+ return out
+
+@frappe.whitelist()
+def download_template():
+ data = frappe._dict(frappe.local.form_dict)
+ fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"]
+ writer = UnicodeWriter()
+
+ writer.writerow([_('Chart of Accounts Template')])
+ writer.writerow([_("Column Labels : ")] + fields)
+ writer.writerow([_("Start entering data from here : ")])
+
+ # download csv file
+ frappe.response['result'] = cstr(writer.getvalue())
+ frappe.response['type'] = 'csv'
+ frappe.response['doctype'] = data.get('doctype')
+
+@frappe.whitelist()
+def validate_accounts(file_name):
+ accounts = generate_data_from_csv(file_name, as_dict=True)
+
+ accounts_dict = {}
+ for account in accounts:
+ accounts_dict.setdefault(account["account_name"], account)
+ if account["parent_account"] and accounts_dict[account["parent_account"]]:
+ accounts_dict[account["parent_account"]]["is_group"] = 1
+
+ message = validate_root(accounts_dict)
+ if message: return message
+ message = validate_account_types(accounts_dict)
+ if message: return message
+
+ return [True, len(accounts)]
+
+def validate_root(accounts):
+ roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
+ if len(roots) < 4:
+ return _("Number of root accounts cannot be less than 4")
+
+ for account in roots:
+ if not account.get("root_type"):
+ return _("Please enter Root Type for - {0}").format(account.get("account_name"))
+ elif account.get("root_type") not in ("Asset", "Liability", "Expense", "Income", "Equity"):
+ return _('Root Type for "{0}" must be one of the Asset, Liability, Income, Expense and Equity').format(account.get("account_name"))
+
+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']]
+
+ missing = list(set(account_types_for_ledger) - set(account_types))
+ if missing:
+ return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
+
+ account_types_for_group = ["Bank", "Cash", "Stock"]
+ account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group']]
+
+ missing = list(set(account_types_for_group) - set(account_groups))
+ if missing:
+ return _("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)
+
+ # remove accounts data from company
+ update_values = {d.fieldname: '' for d in linked}
+ frappe.db.set_value('Company', company, update_values, update_values)
+
+ # remove accounts data from various doctypes
+ for doctype in ["Account", "Party Account", "Mode of Payment Account", "Tax Withholding Account",
+ "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
+ frappe.db.sql('''delete from `tab{0}` where `company`="%s"''' # nosec
+ .format(doctype) % (company))
+
+def set_default_accounts(company):
+ from erpnext.setup.doctype.company.company import install_country_fixtures
+ company = frappe.get_doc('Company', company)
+ company.update({
+ "default_receivable_account": frappe.db.get_value("Account",
+ {"company": company.name, "account_type": "Receivable", "is_group": 0}),
+ "default_payable_account": frappe.db.get_value("Account",
+ {"company": company.name, "account_type": "Payable", "is_group": 0})
+ })
+
+ company.save()
+ install_country_fixtures(company.name)
+ company.create_default_tax_template()
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
new file mode 100644
index 0000000..b075a01
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Chart of Accounts Importer", function (assert) {
+ let done = assert.async();
+
+ // number of asserts
+ assert.expect(1);
+
+ frappe.run_serially([
+ // insert a new Chart of Accounts Importer
+ () => frappe.tests.make('Chart of Accounts Importer', [
+ // values to be set
+ {key: 'value'}
+ ]),
+ () => {
+ assert.equal(cur_frm.doc.key, 'value');
+ },
+ () => done()
+ ]);
+
+});
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py
new file mode 100644
index 0000000..6ab19b7
--- /dev/null
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestChartofAccountsImporter(unittest.TestCase):
+ pass
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index c0d44b2..f7057aa 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -395,7 +395,9 @@
doc.insert()
except frappe.DuplicateEntryError:
doc = frappe.get_doc("Tax Withholding Category", d.get("name"))
- doc.append("accounts", accounts[0])
+
+ if accounts:
+ doc.append("accounts", accounts[0])
# if fiscal year don't match with any of the already entered data, append rate row
fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index b69a64c..4ac11c0 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -276,4 +276,5 @@
frm.set_df_property("create_chart_of_accounts_based_on", "read_only", bool);
frm.set_df_property("chart_of_accounts", "read_only", bool);
frm.set_df_property("existing_company", "read_only", bool);
-};
\ No newline at end of file
+}
+
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index e60e8ad..52c9c5a 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -97,8 +97,6 @@
install_country_fixtures(self.name)
self.create_default_tax_template()
-
-
if not frappe.db.get_value("Department", {"company": self.name}):
from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures
install_post_company_fixtures(frappe._dict({'company_name': self.name}))
@@ -335,6 +333,11 @@
where doctype='Global Defaults' and field='default_company'
and value=%s""", self.name)
+ # reset default company
+ frappe.db.sql("""update `tabSingles` set value=""
+ where doctype='Chart of Accounts Importer' and field='company'
+ and value=%s""", self.name)
+
# delete BOMs
boms = frappe.db.sql_list("select name from tabBOM where company=%s", self.name)
if boms: