# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt import os import json import frappe from frappe import _ def setup_taxes_and_charges(company_name: str, country: str): if not frappe.db.exists('Company', company_name): frappe.throw(_('Company {} does not exist yet. Taxes setup aborted.').format(company_name)) 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) country_wise_tax = tax_data.get(country) 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) update_regional_tax_settings(country, company_name) def simple_to_detailed(templates): """ Convert a simple taxes object into a more detailed data structure. Example input: { "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') coa_data = data.get('chart_of_accounts', {}) tax_templates = coa_data.get(coa_name) or coa_data.get('*', {}) tax_categories = data.get('tax_categories') 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 tax_categories: for tax_category in tax_categories: make_tax_category(tax_category) if sales_tax_templates: for template in sales_tax_templates: make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template) if purchase_tax_templates: for template in purchase_tax_templates: 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 update_regional_tax_settings(country, company): path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country)) if os.path.exists(path.encode("utf-8")): try: module_name = "erpnext.regional.{0}.setup.update_regional_tax_settings".format(frappe.scrub(country)) frappe.get_attr(module_name)(country, company) except Exception as e: # Log error and ignore if failed to setup regional tax settings frappe.log_error() pass def make_taxes_and_charges_template(company_name, doctype, template): template['company'] = company_name template['doctype'] = doctype if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return for tax_row in template.get('taxes'): account_data = tax_row.get('account_head') tax_row_defaults = { 'category': 'Total', 'charge_type': 'On Net Total', 'cost_center': frappe.db.get_value('Company', company_name, 'cost_center') } if doctype == 'Purchase Taxes and Charges Template': tax_row_defaults['add_deduct_tax'] = 'Add' # 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 # 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 doc = frappe.get_doc(template) # Data in country wise json is already pre validated, hence validations can be ignored # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) return doc 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' template['company'] = company_name template['doctype'] = doctype if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): return for tax_row in template.get('taxes'): account_data = tax_row.get('tax_type') # 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') doc = frappe.get_doc(template) # Data in country wise json is already pre validated, hence validations can be ignored # Ingone validations to make doctypes faster doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) return doc def get_or_create_account(company_name, account): """ Check if account already exists. If not, create it. Return a tax account or None. """ default_root_type = 'Liability' root_type = account.get('root_type', default_root_type) existing_accounts = frappe.get_all('Account', filters={ 'company': company_name, 'root_type': root_type }, or_filters={ '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_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 doc = frappe.get_doc(account) doc.flags.ignore_links = True doc.flags.ignore_validate = True doc.insert(ignore_permissions=True, ignore_mandatory=True) return doc 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_name }) if tax_group_name: return tax_group_name # 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, '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_all('Account', { 'is_group': 1, 'root_type': root_type, 'company': company_name, 'report_type': 'Balance Sheet', 'parent_account': ('is', 'not set') }, limit=1)[0] tax_group_account = frappe.get_doc({ 'doctype': 'Account', 'company': company_name, 'is_group': 1, 'report_type': 'Balance Sheet', 'root_type': root_type, 'account_type': 'Tax', 'account_name': account_name, 'parent_account': root_account.name }) tax_group_account.flags.ignore_links = True tax_group_account.flags.ignore_validate = True tax_group_account.insert(ignore_permissions=True) tax_group_name = tax_group_account.name return tax_group_name def make_tax_category(tax_category): doctype = 'Tax Category' if isinstance(tax_category, str): tax_category = {'title': tax_category} tax_category['doctype'] = doctype if not frappe.db.exists(doctype, tax_category['title']): doc = frappe.get_doc(tax_category) doc.insert(ignore_permissions=True)