diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index 1407d5f5fe..d5a36b8259 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -108,9 +108,9 @@ class Account(NestedSet):
parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
["account_name", "account_number"])
- filters = {
+ filters = {
"company": ["in", descendants],
- "account_name": parent_acc_name,
+ "account_name": parent_acc_name,
}
if parent_acc_number:
filters["account_number"] = parent_acc_number
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 40a97ae295..0b7cff3d63 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
@@ -17,17 +17,60 @@ frappe.ui.form.on('Chart of Accounts Importer', {
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", "");
- }
+ download_template: function(frm) {
+ var d = new frappe.ui.Dialog({
+ title: __("Download Template"),
+ fields: [
+ {
+ label : "File Type",
+ fieldname: "file_type",
+ fieldtype: "Select",
+ reqd: 1,
+ options: ["Excel", "CSV"]
+ },
+ {
+ label: "Template Type",
+ fieldname: "template_type",
+ fieldtype: "Select",
+ reqd: 1,
+ options: ["Sample Template", "Blank Template"],
+ change: () => {
+ let template_type = d.get_value('template_type');
+
+ if (template_type === "Sample Template") {
+ d.set_df_property('template_type', 'description',
+ `The Sample Template contains all the required accounts pre filled in the template.
+ You can add more accounts or change existing accounts in the template as per your choice.`);
+ } else {
+ d.set_df_property('template_type', 'description',
+ `The Blank Template contains just the account type and root type required to build the Chart
+ of Accounts. Please enter the account names and add more rows as per your requirement.`);
+ }
+ }
+ }
+ ],
+ primary_action: function() {
+ var data = d.get_values();
+
+ if (!data.template_type) {
+ frappe.throw(__('Please select Template Type to download template'));
+ }
+
+ open_url_post(
+ '/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
+ {
+ file_type: data.file_type,
+ template_type: data.template_type
+ }
+ );
+
+ d.hide();
+ },
+ primary_action_label: __('Download')
+ });
+ d.show();
},
import_file: function (frm) {
@@ -41,21 +84,24 @@ frappe.ui.form.on('Chart of Accounts Importer', {
},
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");
+ if (frm.doc.company) {
+ // 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!
+ Chart Of accounts can be imported for company with no transactions`));
+ } else {
+ frm.trigger("refresh");
+ }
}
- }
- });
+ });
+ }
}
});
@@ -77,7 +123,7 @@ var validate_csv_data = function(frm) {
};
var create_import_button = function(frm) {
- frm.page.set_primary_action(__("Start Import"), function () {
+ frm.page.set_primary_action(__("Import"), function () {
frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: {
@@ -118,7 +164,8 @@ var generate_tree_preview = function(frm) {
args: {
file_name: frm.doc.import_file,
parent: parent,
- doctype: 'Chart of Accounts Importer'
+ 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.json b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json
index d544e69231..ee095ac386 100644
--- 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
@@ -1,226 +1,71 @@
{
- "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,
+ "actions": [],
+ "allow_copy": 1,
+ "creation": "2019-02-01 12:24:34.761380",
"description": "Import Chart of Accounts from a csv file",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Other",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "doctype": "DocType",
+ "document_type": "Other",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "company",
+ "download_template",
+ "import_file",
+ "chart_preview",
+ "chart_tree"
+ ],
"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
- },
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Company",
+ "options": "Company"
+ },
{
- "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": "",
+ "depends_on": "company",
"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
- },
+ "label": "Attach custom Chart of Accounts file"
+ },
{
- "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
- },
+ "fieldtype": "Section Break",
+ "label": "Chart Preview"
+ },
{
- "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
+ "label": "Chart Tree"
+ },
+ {
+ "depends_on": "company",
+ "fieldname": "download_template",
+ "fieldtype": "Button",
+ "label": "Download Template"
}
- ],
- "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",
+ ],
+ "hide_toolbar": 1,
+ "in_create": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-02-28 08:49:11.422846",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Chart of Accounts Importer",
+ "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,
+ "create": 1,
+ "read": 1,
+ "role": "Accounts Manager",
+ "share": 1,
"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
+ ],
+ "quick_entry": 1,
+ "read_only": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ 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
index 362efef46c..b6f5396ccb 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
@@ -4,18 +4,28 @@
from __future__ import unicode_literals
from functools import reduce
-import frappe, csv
+import frappe, csv, os
from frappe import _
-from frappe.utils import cstr
+from frappe.utils import cstr, cint
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
+from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
class ChartofAccountsImporter(Document):
pass
@frappe.whitelist()
def validate_company(company):
+ parent_company, allow_account_creation_against_child_company = frappe.db.get_value('Company',
+ {'name': company}, ['parent_company',
+ 'allow_account_creation_against_child_company'])
+
+ if parent_company and (not allow_account_creation_against_child_company):
+ frappe.throw(_("""{0} is a child company. Please import accounts against parent company
+ or enable {1} in company master""").format(frappe.bold(company),
+ frappe.bold('Allow Account Creation Against Child Company')), title='Wrong Company')
+
if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
return False
@@ -25,42 +35,85 @@ def import_coa(file_name, company):
unset_existing_data(company)
# create accounts
- forest = build_forest(generate_data_from_csv(file_name))
+ file_doc, extension = get_file(file_name)
+
+ if extension == 'csv':
+ data = generate_data_from_csv(file_doc)
+ else:
+ data = generate_data_from_excel(file_doc, extension)
+
+ forest = build_forest(data)
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 '''
- if not file_name.endswith('.csv'):
- frappe.throw("Only CSV files can be used to for importing data. Please check the file format you are trying to upload")
+def get_file(file_name):
+ file_doc = frappe.get_doc("File", {"file_url": file_name})
+ parts = file_doc.get_extension()
+ extension = parts[1]
+ extension = extension.lstrip(".")
+
+ if extension not in ('csv', 'xlsx', 'xls'):
+ frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
+
+ return file_doc, extension
+
+def generate_data_from_csv(file_doc, 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
+ headers = csv_reader[0]
+ del csv_reader[0] # 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)})
+ data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
- if not row[2]: row[2] = row[1]
- data.append(row[1:])
+ if not row[1]: row[1] = row[0]
+ data.append(row)
# convert csv data
return data
+def generate_data_from_excel(file_doc, extension, as_dict=False):
+ content = file_doc.get_content()
+
+ if extension == "xlsx":
+ rows = read_xlsx_file_from_attached_file(fcontent=content)
+ elif extension == "xls":
+ rows = read_xls_file_from_attached_file(content)
+
+ data = []
+ headers = rows[0]
+ del rows[0]
+
+ for row in rows:
+ if as_dict:
+ data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
+ else:
+ if not row[1]: row[1] = row[0]
+ data.append(row)
+
+ return data
+
@frappe.whitelist()
def get_coa(doctype, parent, is_root=False, file_name=None):
''' called by tree view (to fetch node's children) '''
+ file_doc, extension = get_file(file_name)
parent = None if parent==_('All Accounts') else parent
- forest = build_forest(generate_data_from_csv(file_name))
+
+ if extension == 'csv':
+ data = generate_data_from_csv(file_doc)
+ else:
+ data = generate_data_from_excel(file_doc, extension)
+
+ forest = build_forest(data)
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
@@ -91,6 +144,8 @@ def build_forest(data):
# returns the path of any node in list format
def return_parent(data, child):
+ from frappe import _
+
for row in data:
account_name, parent_account = row[0:2]
if parent_account == account_name == child:
@@ -98,8 +153,9 @@ def build_forest(data):
elif account_name == child:
parent_account_list = return_parent(data, parent_account)
if not parent_account_list:
- frappe.throw(_("The parent account {0} does not exists")
- .format(parent_account))
+ frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
+ frappe.bold(parent_account)))
+
return [child] + parent_account_list
charts_map, paths = {}, []
@@ -114,7 +170,7 @@ def build_forest(data):
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
charts_map[account_name] = {}
- if is_group == 1: charts_map[account_name]["is_group"] = is_group
+ 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
if account_number: charts_map[account_name]["account_number"] = account_number
@@ -132,24 +188,94 @@ def build_forest(data):
return out
+def build_response_as_excel(writer):
+ filename = frappe.generate_hash("", 10)
+ with open(filename, 'wb') as f:
+ f.write(cstr(writer.getvalue()).encode('utf-8'))
+ f = open(filename)
+ reader = csv.reader(f)
+
+ from frappe.utils.xlsxutils import make_xlsx
+ xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
+
+ f.close()
+ os.remove(filename)
+
+ # write out response as a xlsx type
+ frappe.response['filename'] = 'coa_importer_template.xlsx'
+ frappe.response['filecontent'] = xlsx_file.getvalue()
+ frappe.response['type'] = 'binary'
+
@frappe.whitelist()
-def download_template():
+def download_template(file_type, template_type):
data = frappe._dict(frappe.local.form_dict)
+
+ writer = get_template(template_type)
+
+ if file_type == 'CSV':
+ # download csv file
+ frappe.response['result'] = cstr(writer.getvalue())
+ frappe.response['type'] = 'csv'
+ frappe.response['doctype'] = 'Chart of Accounts Importer'
+ else:
+ build_response_as_excel(writer)
+
+def get_template(template_type):
+
fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"]
writer = UnicodeWriter()
+ writer.writerow(fields)
- writer.writerow([_('Chart of Accounts Template')])
- writer.writerow([_("Column Labels : ")] + fields)
- writer.writerow([_("Start entering data from here : ")])
+ if template_type == 'Blank Template':
+ for root_type in get_root_types():
+ writer.writerow(['', '', '', 1, '', root_type])
+
+ for account in get_mandatory_group_accounts():
+ writer.writerow(['', '', '', 1, account, "Asset"])
+
+ for account_type in get_mandatory_account_types():
+ writer.writerow(['', '', '', 0, account_type.get('account_type'), account_type.get('root_type')])
+ else:
+ writer = get_sample_template(writer)
+
+ return writer
+
+def get_sample_template(writer):
+ template = [
+ ["Application Of Funds(Assets)", "", "", 1, "", "Asset"],
+ ["Sources Of Funds(Liabilities)", "", "", 1, "", "Liability"],
+ ["Equity", "", "", 1, "", "Equity"],
+ ["Expenses", "", "", 1, "", "Expense"],
+ ["Income", "", "", 1, "", "Income"],
+ ["Bank Accounts", "Application Of Funds(Assets)", "", 1, "Bank", "Asset"],
+ ["Cash In Hand", "Application Of Funds(Assets)", "", 1, "Cash", "Asset"],
+ ["Stock Assets", "Application Of Funds(Assets)", "", 1, "Stock", "Asset"],
+ ["Cost Of Goods Sold", "Expenses", "", 0, "Cost of Goods Sold", "Expense"],
+ ["Asset Depreciation", "Expenses", "", 0, "Depreciation", "Expense"],
+ ["Fixed Assets", "Application Of Funds(Assets)", "", 0, "Fixed Asset", "Asset"],
+ ["Accounts Payable", "Sources Of Funds(Liabilities)", "", 0, "Payable", "Liability"],
+ ["Accounts Receivable", "Application Of Funds(Assets)", "", 1, "Receivable", "Asset"],
+ ["Stock Expenses", "Expenses", "", 0, "Stock Adjustment", "Expense"],
+ ["Sample Bank", "Bank Accounts", "", 0, "Bank", "Asset"],
+ ["Cash", "Cash In Hand", "", 0, "Cash", "Asset"],
+ ["Stores", "Stock Assets", "", 0, "Stock", "Asset"],
+ ]
+
+ for row in template:
+ writer.writerow(row)
+
+ return writer
- # 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)
+
+ file_doc, extension = get_file(file_name)
+
+ if extension == 'csv':
+ accounts = generate_data_from_csv(file_doc, as_dict=True)
+ else:
+ accounts = generate_data_from_excel(file_doc, extension, as_dict=True)
accounts_dict = {}
for account in accounts:
@@ -174,12 +300,38 @@ def validate_root(accounts):
for account in roots:
if not account.get("root_type") and account.get("account_name"):
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
- elif account.get("root_type") not in ("Asset", "Liability", "Expense", "Income", "Equity") and account.get("account_name"):
+ 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")))
if error_messages:
return "
".join(error_messages)
+def get_root_types():
+ return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
+
+def get_report_type(root_type):
+ if root_type in ('Asset', 'Liability', 'Equity'):
+ return 'Balance Sheet'
+ else:
+ return 'Profit and Loss'
+
+def get_mandatory_group_accounts():
+ return ('Bank', 'Cash', 'Stock')
+
+def get_mandatory_account_types():
+ return [
+ {'account_type': 'Cost of Goods Sold', 'root_type': 'Expense'},
+ {'account_type': 'Depreciation', 'root_type': 'Expense'},
+ {'account_type': 'Fixed Asset', 'root_type': 'Asset'},
+ {'account_type': 'Payable', 'root_type': 'Liability'},
+ {'account_type': 'Receivable', 'root_type': 'Asset'},
+ {'account_type': 'Stock Adjustment', 'root_type': 'Expense'},
+ {'account_type': 'Bank', 'root_type': 'Asset'},
+ {'account_type': 'Cash', 'root_type': 'Asset'},
+ {'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 accounts[d]['is_group'] == 1]