2021-03-04 20:11:31 +00:00
|
|
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
2017-12-21 06:25:58 +00:00
|
|
|
# License: GNU General Public License v3. See license.txt
|
|
|
|
|
|
|
|
from __future__ import unicode_literals
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
|
|
|
|
import frappe
|
|
|
|
from frappe import _
|
|
|
|
|
|
|
|
|
|
|
|
def setup_taxes_and_charges(company_name: str, country: str):
|
2021-05-27 12:16:01 +00:00
|
|
|
if not frappe.db.exists('Company', company_name):
|
|
|
|
frappe.throw(_('Company {} does not exist yet. Taxes setup aborted.').format(company_name))
|
|
|
|
|
2021-03-04 20:11:31 +00:00
|
|
|
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)
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
if not country_wise_tax:
|
|
|
|
return
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
if 'chart_of_accounts' not in country_wise_tax:
|
|
|
|
country_wise_tax = simple_to_detailed(country_wise_tax)
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-06-02 11:20:03 +00:00
|
|
|
from_detailed_data(company_name, country_wise_tax)
|
2021-06-02 07:56:21 +00:00
|
|
|
update_regional_tax_settings(country, company_name)
|
2021-03-08 18:53:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
def simple_to_detailed(templates):
|
2021-03-04 20:11:31 +00:00
|
|
|
"""
|
2021-03-08 18:53:50 +00:00
|
|
|
Convert a simple taxes object into a more detailed data structure.
|
|
|
|
|
|
|
|
Example input:
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
{
|
2021-03-08 18:53:50 +00:00
|
|
|
"France VAT 20%": {
|
|
|
|
"account_name": "VAT 20%",
|
|
|
|
"tax_rate": 20,
|
|
|
|
"default": 1
|
|
|
|
},
|
|
|
|
"France VAT 10%": {
|
|
|
|
"account_name": "VAT 10%",
|
|
|
|
"tax_rate": 10
|
2021-03-04 20:11:31 +00:00
|
|
|
}
|
2017-12-21 06:25:58 +00:00
|
|
|
}
|
2021-03-04 20:11:31 +00:00
|
|
|
"""
|
2021-03-08 18:53:50 +00:00
|
|
|
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."""
|
2021-03-04 20:11:31 +00:00
|
|
|
coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts')
|
2021-06-02 11:20:03 +00:00
|
|
|
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('*', {})
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-04-21 15:11:57 +00:00
|
|
|
if tax_categories:
|
|
|
|
for tax_category in tax_categories:
|
|
|
|
make_tax_category(tax_category)
|
|
|
|
|
2021-03-04 20:11:31 +00:00
|
|
|
if sales_tax_templates:
|
|
|
|
for template in sales_tax_templates:
|
2021-03-08 18:53:50 +00:00
|
|
|
make_taxes_and_charges_template(company_name, 'Sales Taxes and Charges Template', template)
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
if purchase_tax_templates:
|
|
|
|
for template in purchase_tax_templates:
|
2021-03-08 18:53:50 +00:00
|
|
|
make_taxes_and_charges_template(company_name, 'Purchase Taxes and Charges Template', template)
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
if item_tax_templates:
|
|
|
|
for template in item_tax_templates:
|
|
|
|
make_item_tax_template(company_name, template)
|
|
|
|
|
|
|
|
|
2021-06-02 07:56:21 +00:00
|
|
|
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
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
def make_taxes_and_charges_template(company_name, doctype, template):
|
|
|
|
template['company'] = company_name
|
|
|
|
template['doctype'] = doctype
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
|
|
|
|
return
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
for tax_row in template.get('taxes'):
|
|
|
|
account_data = tax_row.get('account_head')
|
|
|
|
tax_row_defaults = {
|
2021-03-04 20:11:31 +00:00
|
|
|
'category': 'Total',
|
2021-08-10 11:56:35 +00:00
|
|
|
'charge_type': 'On Net Total',
|
|
|
|
'cost_center': frappe.db.get_value('Company', company_name, 'cost_center')
|
2021-03-04 20:11:31 +00:00
|
|
|
}
|
|
|
|
|
2021-05-31 10:06:20 +00:00
|
|
|
if doctype == 'Purchase Taxes and Charges Template':
|
|
|
|
tax_row_defaults['add_deduct_tax'] = 'Add'
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
# 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
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
# 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
|
|
|
|
|
2021-04-30 11:05:52 +00:00
|
|
|
doc = frappe.get_doc(template)
|
2021-06-23 07:14:56 +00:00
|
|
|
|
|
|
|
# Data in country wise json is already pre validated, hence validations can be ignored
|
|
|
|
# Ingone validations to make doctypes faster
|
2021-04-30 11:05:52 +00:00
|
|
|
doc.flags.ignore_links = True
|
|
|
|
doc.flags.ignore_validate = True
|
|
|
|
doc.insert(ignore_permissions=True)
|
|
|
|
return doc
|
2021-03-04 20:11:31 +00:00
|
|
|
|
|
|
|
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'
|
2021-03-08 18:53:50 +00:00
|
|
|
template['company'] = company_name
|
|
|
|
template['doctype'] = doctype
|
|
|
|
|
2021-03-04 20:11:31 +00:00
|
|
|
if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
|
|
|
|
return
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
for tax_row in template.get('taxes'):
|
|
|
|
account_data = tax_row.get('tax_type')
|
2021-02-24 20:45:29 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
# 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')
|
2021-02-24 20:45:29 +00:00
|
|
|
|
2021-04-30 11:05:52 +00:00
|
|
|
doc = frappe.get_doc(template)
|
2021-06-23 07:14:56 +00:00
|
|
|
|
|
|
|
# Data in country wise json is already pre validated, hence validations can be ignored
|
|
|
|
# Ingone validations to make doctypes faster
|
2021-04-30 11:05:52 +00:00
|
|
|
doc.flags.ignore_links = True
|
|
|
|
doc.flags.ignore_validate = True
|
|
|
|
doc.insert(ignore_permissions=True)
|
|
|
|
return doc
|
2021-02-24 20:45:29 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
def get_or_create_account(company_name, account):
|
2021-03-04 20:11:31 +00:00
|
|
|
"""
|
|
|
|
Check if account already exists. If not, create it.
|
|
|
|
Return a tax account or None.
|
|
|
|
"""
|
2021-03-08 18:53:50 +00:00
|
|
|
default_root_type = 'Liability'
|
|
|
|
root_type = account.get('root_type', default_root_type)
|
2017-12-21 06:25:58 +00:00
|
|
|
|
2021-10-05 09:21:35 +00:00
|
|
|
existing_accounts = frappe.get_all('Account',
|
2021-03-04 20:11:31 +00:00
|
|
|
filters={
|
2021-06-26 18:19:32 +00:00
|
|
|
'company': company_name,
|
|
|
|
'root_type': root_type
|
2021-03-04 20:11:31 +00:00
|
|
|
},
|
|
|
|
or_filters={
|
2021-06-26 18:19:32 +00:00
|
|
|
'account_name': account.get('account_name'),
|
|
|
|
'account_number': account.get('account_number')
|
|
|
|
})
|
|
|
|
|
2021-03-04 20:11:31 +00:00
|
|
|
if existing_accounts:
|
|
|
|
return frappe.get_doc('Account', existing_accounts[0].name)
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
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
|
|
|
|
|
2021-04-30 11:05:52 +00:00
|
|
|
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
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
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', {
|
2021-03-04 20:11:31 +00:00
|
|
|
'is_group': 1,
|
|
|
|
'root_type': root_type,
|
|
|
|
'account_type': 'Tax',
|
2021-03-08 18:53:50 +00:00
|
|
|
'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
|
2021-03-04 20:11:31 +00:00
|
|
|
})
|
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
if tax_group_name:
|
|
|
|
return tax_group_name
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
# Create a new group account named 'Duties and Taxes' or 'Tax Assets' just
|
|
|
|
# below the root account
|
2021-10-05 09:21:35 +00:00
|
|
|
root_account = frappe.get_all('Account', {
|
2021-03-04 20:11:31 +00:00
|
|
|
'is_group': 1,
|
|
|
|
'root_type': root_type,
|
2021-03-08 18:53:50 +00:00
|
|
|
'company': company_name,
|
2021-03-04 20:11:31 +00:00
|
|
|
'report_type': 'Balance Sheet',
|
|
|
|
'parent_account': ('is', 'not set')
|
2021-03-08 18:53:50 +00:00
|
|
|
}, limit=1)[0]
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
tax_group_account = frappe.get_doc({
|
2021-03-04 20:11:31 +00:00
|
|
|
'doctype': 'Account',
|
2021-03-08 18:53:50 +00:00
|
|
|
'company': company_name,
|
2021-03-04 20:11:31 +00:00
|
|
|
'is_group': 1,
|
|
|
|
'report_type': 'Balance Sheet',
|
|
|
|
'root_type': root_type,
|
|
|
|
'account_type': 'Tax',
|
2021-03-08 18:53:50 +00:00
|
|
|
'account_name': account_name,
|
|
|
|
'parent_account': root_account.name
|
2021-04-30 11:05:52 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
tax_group_account.flags.ignore_links = True
|
|
|
|
tax_group_account.flags.ignore_validate = True
|
|
|
|
tax_group_account.insert(ignore_permissions=True)
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
tax_group_name = tax_group_account.name
|
2021-03-04 20:11:31 +00:00
|
|
|
|
2021-03-08 18:53:50 +00:00
|
|
|
return tax_group_name
|
2021-05-27 12:16:26 +00:00
|
|
|
|
|
|
|
|
2021-07-18 12:24:35 +00:00
|
|
|
def make_tax_category(tax_category):
|
2021-06-02 11:20:03 +00:00
|
|
|
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)
|