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