From dcfc3d7d1278d90e0a9154c9d7d7d2dcd2b57bc4 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 19:29:55 +0100 Subject: [PATCH 1/4] fix: remove redundant calls to create_sales_tax --- erpnext/regional/saudi_arabia/setup.py | 5 +---- erpnext/regional/united_arab_emirates/setup.py | 4 +--- erpnext/setup/setup_wizard/utils.py | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index d9ac6cb0f6..9b3677d2c6 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -4,11 +4,8 @@ from __future__ import unicode_literals from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() - - if company: - create_sales_tax(company) \ No newline at end of file diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 776a82c730..d5a29fc200 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -6,15 +6,13 @@ from __future__ import unicode_literals import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property -from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax + def setup(company=None, patch=True): make_custom_fields() add_print_formats() add_custom_roles_for_reports() add_permissions() - if company: - create_sales_tax(company) def make_custom_fields(): is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', diff --git a/erpnext/setup/setup_wizard/utils.py b/erpnext/setup/setup_wizard/utils.py index e82bc96d93..4223f000a6 100644 --- a/erpnext/setup/setup_wizard/utils.py +++ b/erpnext/setup/setup_wizard/utils.py @@ -9,5 +9,4 @@ def complete(): 'data', 'test_mfg.json'), 'r') as f: data = json.loads(f.read()) - #setup_wizard.create_sales_tax(data) setup_complete(data) From 25afad3dc1d75031ba09b405ff8b12de5f0d8007 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Mar 2021 21:11:31 +0100 Subject: [PATCH 2/4] refactor: extend taxes and charges setup Add option to specify taxes and charges template depending on the CoA used. Differentiate between purchase, sales and item taxes. Maintain flexibility by using wildcards. --- erpnext/setup/doctype/company/company.py | 7 +- .../setup_wizard/data/country_wise_tax.json | 307 ++++++++++++++-- .../setup_wizard/operations/taxes_setup.py | 330 ++++++++++++------ 3 files changed, 513 insertions(+), 131 deletions(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 819ba78e66..d3021bafea 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -17,6 +17,7 @@ from frappe.utils.nestedset import NestedSet from past.builtins import cmp import functools from erpnext.accounts.doctype.account.account import get_account_currency +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges class Company(NestedSet): nsm_parent_field = 'parent_company' @@ -67,11 +68,7 @@ class Company(NestedSet): frappe.throw(_("Abbreviation already used for another company")) def create_default_tax_template(self): - from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax - create_sales_tax({ - 'country': self.country, - 'company_name': self.name - }) + setup_taxes_and_charges(self.name, self.country) def validate_default_accounts(self): accounts = [ diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index beddaeed79..9ccbdb965b 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -481,14 +481,230 @@ }, "Germany": { - "Germany VAT 19%": { - "account_name": "VAT 19%", - "tax_rate": 19.00, - "default": 1 - }, - "Germany VAT 7%": { - "account_name": "VAT 7%", - "tax_rate": 7.00 + "chart_of_accounts": { + "SKR04 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + }, + { + "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00, + "add_deduct_tax": "Add" + }, + { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00, + "add_deduct_tax": "Deduct" + } + ] + } + ] + }, + "SKR03 mit Kontonummern": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "Standard with Numbers": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + }, + "*": { + "sales_tax_templates": [ + { + "title": "Umsatzsteuer 19%", + "is_default": 1, + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 19%", + "rate": 19.00 + } + ] + }, + { + "title": "Umsatzsteuer 7%", + "accounts": [ + { + "type": "On Net Total", + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } + ] + } + ], + "purchase_tax_templates": [ + { + "title": "Abziehbare Vorsteuer 19%", + "is_default": 1, + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 19%", + "root_type": "Asset", + "tax_rate": 19.00 + } + ] + }, + { + "title": "Abziehbare Vorsteuer 7%", + "accounts": [ + { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } + ] + } + ] + } } }, @@ -580,26 +796,61 @@ }, "India": { - "In State GST": { - "account_name": ["SGST", "CGST"], - "tax_rate": [9.00, 9.00], - "default": 1 - }, - "Out of State GST": { - "account_name": "IGST", - "tax_rate": 18.00 - }, - "VAT 5%": { - "account_name": "VAT 5%", - "tax_rate": 5.00 - }, - "VAT 4%": { - "account_name": "VAT 4%", - "tax_rate": 4.00 - }, - "VAT 14%": { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "chart_of_accounts": { + "*": { + "*": [ + { + "title": "In State GST", + "is_default": 1, + "accounts": [ + { + "account_name": "SGST", + "tax_rate": 9.00 + }, + { + "account_name": "CGST", + "tax_rate": 9.00 + } + ] + }, + { + "title": "Out of State GST", + "accounts": [ + { + "account_name": "IGST", + "tax_rate": 18.00 + } + ] + }, + { + "title": "VAT 5%", + "accounts": [ + { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + ] + }, + { + "title": "VAT 4%", + "accounts": [ + { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + ] + }, + { + "title": "VAT 14%", + "accounts": [ + { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + ] + } + ] + } } }, diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index c3c1593c04..81506c4352 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -1,123 +1,257 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, copy, os, json -from frappe.utils import flt -from erpnext.accounts.doctype.account.account import RootNotEditable -def create_sales_tax(args): - country_wise_tax = get_country_wise_tax(args.get("country")) - if country_wise_tax and len(country_wise_tax) > 0: - for sales_tax, tax_data in country_wise_tax.items(): - make_tax_account_and_template( - args.get("company_name"), - tax_data.get('account_name'), - tax_data.get('tax_rate'), sales_tax) +import os +import json -def make_tax_account_and_template(company, account_name, tax_rate, template_name=None): - if not isinstance(account_name, (list, tuple)): - account_name = [account_name] - tax_rate = [tax_rate] +import frappe +from frappe import _ - accounts = [] - for i, name in enumerate(account_name): - tax_account = make_tax_account(company, account_name[i], tax_rate[i]) - if tax_account: - accounts.append(tax_account) - try: - if accounts: - make_sales_and_purchase_tax_templates(accounts, template_name) - make_item_tax_templates(accounts, template_name) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - except RootNotEditable: - pass +def setup_taxes_and_charges(company_name: str, country: str): + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'country_wise_tax.json') + with open(file_path, 'r') as json_file: + tax_data = json.load(json_file) -def make_tax_account(company, account_name, tax_rate): - tax_group = get_tax_account_group(company) - if tax_group: - try: - return frappe.get_doc({ - "doctype":"Account", - "company": company, - "parent_account": tax_group, - "account_name": account_name, - "is_group": 0, - "report_type": "Balance Sheet", - "root_type": "Liability", - "account_type": "Tax", - "tax_rate": flt(tax_rate) if tax_rate else None - }).insert(ignore_permissions=True, ignore_mandatory=True) - except frappe.NameError: - if frappe.message_log: frappe.message_log.pop() - abbr = frappe.get_cached_value('Company', company, 'abbr') - account = '{0} - {1}'.format(account_name, abbr) - return frappe.get_doc('Account', account) + country_wise_tax = tax_data.get(country) -def make_sales_and_purchase_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + if country_wise_tax: + if 'chart_of_accounts' in country_wise_tax: + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) + else: + from_simple_data(company_name, country_wise_tax) - sales_tax_template = { - "doctype": "Sales Taxes and Charges Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + +def from_detailed_data(company_name, data): + """ + Create Taxes and Charges Templates from detailed data like this: + + { + "chart_of_accounts": { + coa_name: { + "sales_tax_templates": [ + { + 'title': '', + 'is_default': 1, + 'accounts': [ + { + 'account_name': '', + 'account_number': '', + 'root_type': '', + } + ] + } + ], + "purchase_tax_templates": [ ... ], + "item_tax_templates": [ ... ], + "*": [ ... ] + } + } } + """ + coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') + tax_templates = data.get(coa_name) or data.get('*') + sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') + purchase_tax_templates = tax_templates.get('purchase_tax_templates') or tax_templates.get('*') + item_tax_templates = tax_templates.get('item_tax_templates') or tax_templates.get('*') + + if sales_tax_templates: + for template in sales_tax_templates: + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + + if purchase_tax_templates: + for template in purchase_tax_templates: + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + + if item_tax_templates: + for template in item_tax_templates: + make_item_tax_template(company_name, template) + + +def from_simple_data(company_name, data): + """ + Create Taxes and Charges Templates from simple data like this: + + "Austria Tax": { + "account_name": "VAT", + "tax_rate": 20.00 + } + """ + for template_name, tax_data in data.items(): + template = { + 'title': template_name, + 'is_default': tax_data.get('default'), + 'accounts': [ + { + 'account_name': tax_data.get('account_name'), + 'tax_rate': tax_data.get('tax_rate') + } + ] + } + make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_item_tax_template(company_name, template) + + +def make_tax_template(company_name, doctype, template): + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) + + # Get all fields of the Taxes and Charges Template + tax_template = {'doctype': doctype} + tax_template_fields = frappe.get_meta(doctype).fields + tax_template_fieldnames = [field.fieldname for field in tax_template_fields] + + # Get all fields of the taxes child table + table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] + table_fields = frappe.get_meta(table_doctype).fields + table_field_names = [field.fieldname for field in table_fields] + + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in tax_template_fieldnames: + if field in template: + tax_template[field] = template.get(field) + + # However, company always fixed and taxes table must be empty to start with + tax_template['company'] = company_name + tax_template['taxes'] = [] for account in accounts: - sales_tax_template['taxes'].append({ - "category": "Total", - "charge_type": "On Net Total", - "account_head": account.name, - "description": "{0} @ {1}".format(account.account_name, account.tax_rate), - "rate": account.tax_rate - }) - # Sales - frappe.get_doc(copy.deepcopy(sales_tax_template)).insert(ignore_permissions=True) + row = { + 'category': 'Total', + 'charge_type': 'On Net Total', + 'account_head': account.get('name'), + 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), + 'rate': account.get('tax_rate') + } + # Check if field exists as a key in the import data and, if yes, set the + # value accordingly + for field in table_field_names: + if field in account: + row[field] = account.get(field) - # Purchase - purchase_tax_template = copy.deepcopy(sales_tax_template) - purchase_tax_template["doctype"] = "Purchase Taxes and Charges Template" + tax_template['taxes'].append(row) - doc = frappe.get_doc(purchase_tax_template) - doc.insert(ignore_permissions=True) + return frappe.get_doc(tax_template).insert(ignore_permissions=True) -def make_item_tax_templates(accounts, template_name=None): - if not template_name: - template_name = accounts[0].name + +def make_item_tax_template(company_name, template): + """Create an Item Tax Template. + + This requires a separate method because Item Tax Template is structured + differently from Sales and Purchase Tax Templates. + """ + doctype = 'Item Tax Template' + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): + return + + accounts = get_or_create_accounts(company_name, template.get('accounts')) item_tax_template = { - "doctype": "Item Tax Template", - "title": template_name, - "company": accounts[0].company, - 'taxes': [] + 'doctype': doctype, + 'title': template.get('title'), + 'company': company_name, + 'taxes': [{ + 'tax_type': account.get('name'), + 'tax_rate': account.get('tax_rate') + } for account in accounts] } + return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) - for account in accounts: - item_tax_template['taxes'].append({ - "tax_type": account.name, - "tax_rate": account.tax_rate - }) - # Items - frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) +def get_or_create_accounts(company: str, account_data: list): + for account in account_data: + if 'creation' in account: + # Hack to check if account already contains a real Account doc + # or just the attibutes from country_wise_tax.json + continue -def get_tax_account_group(company): - tax_group = frappe.db.get_value("Account", - {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) - if not tax_group: - tax_group = frappe.db.get_value("Account", {"is_group": 1, "root_type": "Liability", - "account_type": "Tax", "company": company}) + # tax_rate should survive the following lines because it might not be + # specified in an existing account or different rates might get booked + # onto the same account. + tax_rate = account.get('tax_rate') + doc = get_or_create_account(company, account) + account.update(doc.as_dict()) + account['tax_rate'] = tax_rate + + return account_data + + +def get_or_create_account(company, account_data): + """ + Check if account already exists. If not, create it. + Return a tax account or None. + """ + root_type = account_data.get('root_type', 'Liability') + account_name = account_data.get('account_name') + account_number = account_data.get('account_number') + + existing_accounts = frappe.get_list('Account', + filters={ + 'company': company, + 'root_type': root_type + }, + or_filters={ + 'account_name': account_name, + 'account_number': account_number + } + ) + + if existing_accounts: + return frappe.get_doc('Account', existing_accounts[0].name) + + tax_group = get_or_create_tax_account_group(company, root_type) + full_account_data = { + 'doctype': 'Account', + 'account_name': account_name, + 'account_number': account_number, + 'tax_rate': account_data.get('tax_rate'), + 'company': company, + 'parent_account': tax_group, + 'is_group': 0, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax' + } + return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + + +def get_or_create_tax_account_group(company, root_type): + tax_group = frappe.db.get_value('Account', { + 'is_group': 1, + 'root_type': root_type, + 'account_type': 'Tax', + 'company': company + }) + + if tax_group: + return tax_group + + root = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company, + 'report_type': 'Balance Sheet', + 'parent_account': ('is', 'not set') + }, limit=1)[0].name + + doc = frappe.get_doc({ + 'doctype': 'Account', + 'company': company, + 'is_group': 1, + 'report_type': 'Balance Sheet', + 'root_type': root_type, + 'account_type': 'Tax', + 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), + 'parent_account': root + }).insert(ignore_permissions=True) + + tax_group = doc.name return tax_group - -def get_country_wise_tax(country): - data = {} - with open (os.path.join(os.path.dirname(__file__), "..", "data", "country_wise_tax.json")) as countrywise_tax: - data = json.load(countrywise_tax).get(country) - - return data From ebd1d08e55b8146e652096d2c591754f8d676504 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:53:50 +0100 Subject: [PATCH 3/4] refactor: taxes setup Better structure of input data. --- .../setup_wizard/data/country_wise_tax.json | 310 ++++++++++++------ .../setup_wizard/operations/taxes_setup.py | 279 +++++++--------- 2 files changed, 334 insertions(+), 255 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 9ccbdb965b..6305442ef2 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -487,23 +487,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "3806", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "3806", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "3801", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "3801", + "tax_rate": 7.00 + } } ] } @@ -512,41 +514,49 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1406", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1406", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1401", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1401", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] }, { "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", - "account_number": "1407", - "root_type": "Asset", - "tax_rate": 19.00, + "account_head": { + "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", + "account_number": "1407", + "root_type": "Asset", + "tax_rate": 19.00 + }, "add_deduct_tax": "Add" }, { - "account_name": "Umsatzsteuer nach § 13b UStG 19%", - "account_number": "3837", - "root_type": "Liability", - "tax_rate": 19.00, + "account_head": { + "account_name": "Umsatzsteuer nach § 13b UStG 19%", + "account_number": "3837", + "root_type": "Liability", + "tax_rate": 19.00 + }, "add_deduct_tax": "Deduct" } ] @@ -558,23 +568,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "1776", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "1776", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "1771", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "1771", + "tax_rate": 7.00 + } } ] } @@ -583,23 +595,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1576", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1576", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1571", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1571", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -610,23 +626,25 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "account_number": "2301", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "account_number": "2301", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "account_number": "2302", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "account_number": "2302", + "tax_rate": 7.00 + } } ] } @@ -635,23 +653,27 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "account_number": "1501", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "account_number": "1501", + "root_type": "Asset", + "tax_rate": 19.00 + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "account_number": "1502", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "account_number": "1502", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -662,21 +684,23 @@ { "title": "Umsatzsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 19%", - "rate": 19.00 + "account_head": { + "account_name": "Umsatzsteuer 19%", + "tax_rate": 19.00 + } } ] }, { "title": "Umsatzsteuer 7%", - "accounts": [ + "taxes": [ { - "type": "On Net Total", - "account_name": "Umsatzsteuer 7%", - "tax_rate": 7.00 + "account_head": { + "account_name": "Umsatzsteuer 7%", + "tax_rate": 7.00 + } } ] } @@ -685,21 +709,25 @@ { "title": "Abziehbare Vorsteuer 19%", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 19%", - "root_type": "Asset", - "tax_rate": 19.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 19%", + "tax_rate": 19.00, + "root_type": "Asset" + } } ] }, { "title": "Abziehbare Vorsteuer 7%", - "accounts": [ + "taxes": [ { - "account_name": "Abziehbare Vorsteuer 7%", - "root_type": "Asset", - "tax_rate": 7.00 + "account_head": { + "account_name": "Abziehbare Vorsteuer 7%", + "root_type": "Asset", + "tax_rate": 7.00 + } } ] } @@ -798,54 +826,130 @@ "India": { "chart_of_accounts": { "*": { - "*": [ + "item_tax_templates": [ { "title": "In State GST", "is_default": 1, - "accounts": [ + "taxes": [ { - "account_name": "SGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "SGST", + "tax_rate": 9.00 + } }, { - "account_name": "CGST", - "tax_rate": 9.00 + "tax_type": { + "account_name": "CGST", + "tax_rate": 9.00 + } } ] }, { "title": "Out of State GST", - "accounts": [ + "taxes": [ { - "account_name": "IGST", - "tax_rate": 18.00 + "tax_type": { + "account_name": "IGST", + "tax_rate": 18.00 + } } ] }, { "title": "VAT 5%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 5%", - "tax_rate": 5.00 + "tax_type": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } } ] }, { "title": "VAT 4%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 4%", - "tax_rate": 4.00 + "tax_type": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } } ] }, { "title": "VAT 14%", - "accounts": [ + "taxes": [ { - "account_name": "VAT 14%", - "tax_rate": 14.00 + "tax_type": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } + } + ] + } + ], + "*": [ + { + "title": "In State GST", + "is_default": 1, + "taxes": [ + { + "account_head": { + "account_name": "SGST", + "tax_rate": 9.00 + } + }, + { + "account_head": { + "account_name": "CGST", + "tax_rate": 9.00 + } + } + ] + }, + { + "title": "Out of State GST", + "taxes": [ + { + "account_head": { + "account_name": "IGST", + "tax_rate": 18.00 + } + } + ] + }, + { + "title": "VAT 5%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 5%", + "tax_rate": 5.00 + } + } + ] + }, + { + "title": "VAT 4%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 4%", + "tax_rate": 4.00 + } + } + ] + }, + { + "title": "VAT 14%", + "taxes": [ + { + "account_head": { + "account_name": "VAT 14%", + "tax_rate": 14.00 + } } ] } diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index 81506c4352..429a558c58 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -17,40 +17,62 @@ def setup_taxes_and_charges(company_name: str, country: str): country_wise_tax = tax_data.get(country) - if country_wise_tax: - if 'chart_of_accounts' in country_wise_tax: - from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) - else: - from_simple_data(company_name, country_wise_tax) + if not country_wise_tax: + return + + if 'chart_of_accounts' not in country_wise_tax: + country_wise_tax = simple_to_detailed(country_wise_tax) + + from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts')) -def from_detailed_data(company_name, data): +def simple_to_detailed(templates): """ - Create Taxes and Charges Templates from detailed data like this: + Convert a simple taxes object into a more detailed data structure. + + Example input: { - "chart_of_accounts": { - coa_name: { - "sales_tax_templates": [ - { - 'title': '', - 'is_default': 1, - 'accounts': [ - { - 'account_name': '', - 'account_number': '', - 'root_type': '', - } - ] - } - ], - "purchase_tax_templates": [ ... ], - "item_tax_templates": [ ... ], - "*": [ ... ] - } + "France VAT 20%": { + "account_name": "VAT 20%", + "tax_rate": 20, + "default": 1 + }, + "France VAT 10%": { + "account_name": "VAT 10%", + "tax_rate": 10 } } """ + return { + 'chart_of_accounts': { + '*': { + 'item_tax_templates': [{ + 'title': title, + 'taxes': [{ + 'tax_type': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()], + '*': [{ + 'title': title, + 'is_default': data.get('default', 0), + 'taxes': [{ + 'account_head': { + 'account_name': data.get('account_name'), + 'tax_rate': data.get('tax_rate') + } + }] + } for title, data in templates.items()] + } + } + } + + +def from_detailed_data(company_name, data): + """Create Taxes and Charges Templates from detailed data.""" coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts') tax_templates = data.get(coa_name) or data.get('*') sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.get('*') @@ -59,85 +81,44 @@ def from_detailed_data(company_name, data): if sales_tax_templates: for template in sales_tax_templates: - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) if purchase_tax_templates: for template in purchase_tax_templates: - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) + make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template) if item_tax_templates: for template in item_tax_templates: make_item_tax_template(company_name, template) -def from_simple_data(company_name, data): - """ - Create Taxes and Charges Templates from simple data like this: +def make_taxes_and_charges_template(company_name, doctype, template): + template['company'] = company_name + template['doctype'] = doctype - "Austria Tax": { - "account_name": "VAT", - "tax_rate": 20.00 - } - """ - for template_name, tax_data in data.items(): - template = { - 'title': template_name, - 'is_default': tax_data.get('default'), - 'accounts': [ - { - 'account_name': tax_data.get('account_name'), - 'tax_rate': tax_data.get('tax_rate') - } - ] - } - make_tax_template(company_name, 'Sales Taxes and Charges Template', template) - make_tax_template(company_name, 'Purchase Taxes and Charges Template', template) - make_item_tax_template(company_name, template) - - -def make_tax_template(company_name, doctype, template): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) - - # Get all fields of the Taxes and Charges Template - tax_template = {'doctype': doctype} - tax_template_fields = frappe.get_meta(doctype).fields - tax_template_fieldnames = [field.fieldname for field in tax_template_fields] - - # Get all fields of the taxes child table - table_doctype = [field.options for field in tax_template_fields if field.fieldname=='taxes'][0] - table_fields = frappe.get_meta(table_doctype).fields - table_field_names = [field.fieldname for field in table_fields] - - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in tax_template_fieldnames: - if field in template: - tax_template[field] = template.get(field) - - # However, company always fixed and taxes table must be empty to start with - tax_template['company'] = company_name - tax_template['taxes'] = [] - - for account in accounts: - row = { + for tax_row in template.get('taxes'): + account_data = tax_row.get('account_head') + tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total', - 'account_head': account.get('name'), - 'description': '{0} @ {1}'.format(account.get('account_name'), account.get('tax_rate')), - 'rate': account.get('tax_rate') + 'charge_type': 'On Net Total' } - # Check if field exists as a key in the import data and, if yes, set the - # value accordingly - for field in table_field_names: - if field in account: - row[field] = account.get(field) - tax_template['taxes'].append(row) + # if account_head is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + tax_row_defaults['description'] = '{0} @ {1}'.format(account_data.get('account_name'), account_data.get('tax_rate')) + tax_row_defaults['rate'] = account_data.get('tax_rate') + account = get_or_create_account(company_name, account_data) + tax_row['account_head'] = account.name - return frappe.get_doc(tax_template).insert(ignore_permissions=True) + # use the default value if nothing other is specified + for fieldname, default_value in tax_row_defaults.items(): + if fieldname not in tax_row: + tax_row[fieldname] = default_value + + return frappe.get_doc(template).insert(ignore_permissions=True) def make_item_tax_template(company_name, template): @@ -147,111 +128,105 @@ def make_item_tax_template(company_name, template): differently from Sales and Purchase Tax Templates. """ doctype = 'Item Tax Template' + template['company'] = company_name + template['doctype'] = doctype + if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return - accounts = get_or_create_accounts(company_name, template.get('accounts')) + for tax_row in template.get('taxes'): + account_data = tax_row.get('tax_type') - item_tax_template = { - 'doctype': doctype, - 'title': template.get('title'), - 'company': company_name, - 'taxes': [{ - 'tax_type': account.get('name'), - 'tax_rate': account.get('tax_rate') - } for account in accounts] - } + # if tax_type is a dict, search or create the account and get it's name + if isinstance(account_data, dict): + account = get_or_create_account(company_name, account_data) + tax_row['tax_type'] = account.name + if 'tax_rate' not in tax_row: + tax_row['tax_rate'] = account_data.get('tax_rate') - return frappe.get_doc(item_tax_template).insert(ignore_permissions=True) + return frappe.get_doc(template).insert(ignore_permissions=True) -def get_or_create_accounts(company: str, account_data: list): - for account in account_data: - if 'creation' in account: - # Hack to check if account already contains a real Account doc - # or just the attibutes from country_wise_tax.json - continue - - # tax_rate should survive the following lines because it might not be - # specified in an existing account or different rates might get booked - # onto the same account. - tax_rate = account.get('tax_rate') - doc = get_or_create_account(company, account) - account.update(doc.as_dict()) - account['tax_rate'] = tax_rate - - return account_data - - -def get_or_create_account(company, account_data): +def get_or_create_account(company_name, account): """ Check if account already exists. If not, create it. Return a tax account or None. """ - root_type = account_data.get('root_type', 'Liability') - account_name = account_data.get('account_name') - account_number = account_data.get('account_number') + default_root_type = 'Liability' + root_type = account.get('root_type', default_root_type) existing_accounts = frappe.get_list('Account', filters={ - 'company': company, + 'company': company_name, 'root_type': root_type }, or_filters={ - 'account_name': account_name, - 'account_number': account_number + 'account_name': account.get('account_name'), + 'account_number': account.get('account_number') } ) if existing_accounts: return frappe.get_doc('Account', existing_accounts[0].name) - tax_group = get_or_create_tax_account_group(company, root_type) - full_account_data = { - 'doctype': 'Account', - 'account_name': account_name, - 'account_number': account_number, - 'tax_rate': account_data.get('tax_rate'), - 'company': company, - 'parent_account': tax_group, - 'is_group': 0, - 'report_type': 'Balance Sheet', - 'root_type': root_type, - 'account_type': 'Tax' - } - return frappe.get_doc(full_account_data).insert(ignore_permissions=True, ignore_mandatory=True) + tax_group = get_or_create_tax_group(company_name, root_type) + + account['doctype'] = 'Account' + account['company'] = company_name + account['parent_account'] = tax_group + account['report_type'] = 'Balance Sheet' + account['account_type'] = 'Tax' + account['root_type'] = root_type + account['is_group'] = 0 + + return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True) -def get_or_create_tax_account_group(company, root_type): - tax_group = frappe.db.get_value('Account', { +def get_or_create_tax_group(company_name, root_type): + # Look for a group account of type 'Tax' + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, 'account_type': 'Tax', - 'company': company + 'company': company_name }) - if tax_group: - return tax_group + if tax_group_name: + return tax_group_name - root = frappe.get_list('Account', { + # Look for a group account named 'Duties and Taxes' or 'Tax Assets' + account_name = _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets') + tax_group_name = frappe.db.get_value('Account', { 'is_group': 1, 'root_type': root_type, - 'company': company, + 'account_name': account_name, + 'company': company_name + }) + + if tax_group_name: + return tax_group_name + + # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just + # below the root account + root_account = frappe.get_list('Account', { + 'is_group': 1, + 'root_type': root_type, + 'company': company_name, 'report_type': 'Balance Sheet', 'parent_account': ('is', 'not set') - }, limit=1)[0].name + }, limit=1)[0] - doc = frappe.get_doc({ + tax_group_account = frappe.get_doc({ 'doctype': 'Account', - 'company': company, + 'company': company_name, 'is_group': 1, 'report_type': 'Balance Sheet', 'root_type': root_type, 'account_type': 'Tax', - 'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), - 'parent_account': root + 'account_name': account_name, + 'parent_account': root_account.name }).insert(ignore_permissions=True) - tax_group = doc.name + tax_group_name = tax_group_account.name - return tax_group + return tax_group_name From 80d44cada4248946ed6e0cc8e61b09d2612b1547 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Sun, 18 Apr 2021 18:33:34 +0200 Subject: [PATCH 4/4] fix: remove is_default from country wise tax --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 6305442ef2..5876488033 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -486,7 +486,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -513,7 +512,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -567,7 +565,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -594,7 +591,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -625,7 +621,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -652,7 +647,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -683,7 +677,6 @@ "sales_tax_templates": [ { "title": "Umsatzsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -708,7 +701,6 @@ "purchase_tax_templates": [ { "title": "Abziehbare Vorsteuer 19%", - "is_default": 1, "taxes": [ { "account_head": { @@ -829,7 +821,6 @@ "item_tax_templates": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "tax_type": { @@ -893,7 +884,6 @@ "*": [ { "title": "In State GST", - "is_default": 1, "taxes": [ { "account_head": {