refactor: taxes setup

Better structure of input data.
This commit is contained in:
barredterra 2021-03-08 19:53:50 +01:00
parent 25afad3dc1
commit ebd1d08e55
2 changed files with 334 additions and 255 deletions

View File

@ -487,24 +487,26 @@
{ {
"title": "Umsatzsteuer 19%", "title": "Umsatzsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 19%", "account_name": "Umsatzsteuer 19%",
"account_number": "3806", "account_number": "3806",
"rate": 19.00 "tax_rate": 19.00
}
} }
] ]
}, },
{ {
"title": "Umsatzsteuer 7%", "title": "Umsatzsteuer 7%",
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 7%", "account_name": "Umsatzsteuer 7%",
"account_number": "3801", "account_number": "3801",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
], ],
@ -512,41 +514,49 @@
{ {
"title": "Abziehbare Vorsteuer 19%", "title": "Abziehbare Vorsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 19%", "account_name": "Abziehbare Vorsteuer 19%",
"account_number": "1406", "account_number": "1406",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
} }
}
] ]
}, },
{ {
"title": "Abziehbare Vorsteuer 7%", "title": "Abziehbare Vorsteuer 7%",
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 7%", "account_name": "Abziehbare Vorsteuer 7%",
"account_number": "1401", "account_number": "1401",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
}, },
{ {
"title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer", "title": "Innergemeinschaftlicher Erwerb 19% Umsatzsteuer und 19% Vorsteuer",
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%", "account_name": "Abziehbare Vorsteuer nach § 13b UStG 19%",
"account_number": "1407", "account_number": "1407",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00, "tax_rate": 19.00
},
"add_deduct_tax": "Add" "add_deduct_tax": "Add"
}, },
{ {
"account_head": {
"account_name": "Umsatzsteuer nach § 13b UStG 19%", "account_name": "Umsatzsteuer nach § 13b UStG 19%",
"account_number": "3837", "account_number": "3837",
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00, "tax_rate": 19.00
},
"add_deduct_tax": "Deduct" "add_deduct_tax": "Deduct"
} }
] ]
@ -558,24 +568,26 @@
{ {
"title": "Umsatzsteuer 19%", "title": "Umsatzsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 19%", "account_name": "Umsatzsteuer 19%",
"account_number": "1776", "account_number": "1776",
"rate": 19.00 "tax_rate": 19.00
}
} }
] ]
}, },
{ {
"title": "Umsatzsteuer 7%", "title": "Umsatzsteuer 7%",
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 7%", "account_name": "Umsatzsteuer 7%",
"account_number": "1771", "account_number": "1771",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
], ],
@ -583,24 +595,28 @@
{ {
"title": "Abziehbare Vorsteuer 19%", "title": "Abziehbare Vorsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 19%", "account_name": "Abziehbare Vorsteuer 19%",
"account_number": "1576", "account_number": "1576",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
} }
}
] ]
}, },
{ {
"title": "Abziehbare Vorsteuer 7%", "title": "Abziehbare Vorsteuer 7%",
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 7%", "account_name": "Abziehbare Vorsteuer 7%",
"account_number": "1571", "account_number": "1571",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
] ]
@ -610,24 +626,26 @@
{ {
"title": "Umsatzsteuer 19%", "title": "Umsatzsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 19%", "account_name": "Umsatzsteuer 19%",
"account_number": "2301", "account_number": "2301",
"rate": 19.00 "tax_rate": 19.00
}
} }
] ]
}, },
{ {
"title": "Umsatzsteuer 7%", "title": "Umsatzsteuer 7%",
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 7%", "account_name": "Umsatzsteuer 7%",
"account_number": "2302", "account_number": "2302",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
], ],
@ -635,24 +653,28 @@
{ {
"title": "Abziehbare Vorsteuer 19%", "title": "Abziehbare Vorsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 19%", "account_name": "Abziehbare Vorsteuer 19%",
"account_number": "1501", "account_number": "1501",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
} }
}
] ]
}, },
{ {
"title": "Abziehbare Vorsteuer 7%", "title": "Abziehbare Vorsteuer 7%",
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 7%", "account_name": "Abziehbare Vorsteuer 7%",
"account_number": "1502", "account_number": "1502",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
] ]
@ -662,22 +684,24 @@
{ {
"title": "Umsatzsteuer 19%", "title": "Umsatzsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 19%", "account_name": "Umsatzsteuer 19%",
"rate": 19.00 "tax_rate": 19.00
}
} }
] ]
}, },
{ {
"title": "Umsatzsteuer 7%", "title": "Umsatzsteuer 7%",
"accounts": [ "taxes": [
{ {
"type": "On Net Total", "account_head": {
"account_name": "Umsatzsteuer 7%", "account_name": "Umsatzsteuer 7%",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
], ],
@ -685,22 +709,26 @@
{ {
"title": "Abziehbare Vorsteuer 19%", "title": "Abziehbare Vorsteuer 19%",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 19%", "account_name": "Abziehbare Vorsteuer 19%",
"root_type": "Asset", "tax_rate": 19.00,
"tax_rate": 19.00 "root_type": "Asset"
}
} }
] ]
}, },
{ {
"title": "Abziehbare Vorsteuer 7%", "title": "Abziehbare Vorsteuer 7%",
"accounts": [ "taxes": [
{ {
"account_head": {
"account_name": "Abziehbare Vorsteuer 7%", "account_name": "Abziehbare Vorsteuer 7%",
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
} }
}
] ]
} }
] ]
@ -798,55 +826,131 @@
"India": { "India": {
"chart_of_accounts": { "chart_of_accounts": {
"*": { "*": {
"*": [ "item_tax_templates": [
{ {
"title": "In State GST", "title": "In State GST",
"is_default": 1, "is_default": 1,
"accounts": [ "taxes": [
{ {
"tax_type": {
"account_name": "SGST", "account_name": "SGST",
"tax_rate": 9.00 "tax_rate": 9.00
}
}, },
{ {
"tax_type": {
"account_name": "CGST", "account_name": "CGST",
"tax_rate": 9.00 "tax_rate": 9.00
} }
}
] ]
}, },
{ {
"title": "Out of State GST", "title": "Out of State GST",
"accounts": [ "taxes": [
{ {
"tax_type": {
"account_name": "IGST", "account_name": "IGST",
"tax_rate": 18.00 "tax_rate": 18.00
} }
}
] ]
}, },
{ {
"title": "VAT 5%", "title": "VAT 5%",
"accounts": [ "taxes": [
{ {
"tax_type": {
"account_name": "VAT 5%", "account_name": "VAT 5%",
"tax_rate": 5.00 "tax_rate": 5.00
} }
}
] ]
}, },
{ {
"title": "VAT 4%", "title": "VAT 4%",
"accounts": [ "taxes": [
{ {
"tax_type": {
"account_name": "VAT 4%", "account_name": "VAT 4%",
"tax_rate": 4.00 "tax_rate": 4.00
} }
}
] ]
}, },
{ {
"title": "VAT 14%", "title": "VAT 14%",
"accounts": [ "taxes": [
{ {
"tax_type": {
"account_name": "VAT 14%", "account_name": "VAT 14%",
"tax_rate": 14.00 "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
}
}
] ]
} }
] ]

View File

@ -17,40 +17,62 @@ def setup_taxes_and_charges(company_name: str, country: str):
country_wise_tax = tax_data.get(country) country_wise_tax = tax_data.get(country)
if country_wise_tax: if not country_wise_tax:
if 'chart_of_accounts' in 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')) from_detailed_data(company_name, country_wise_tax.get('chart_of_accounts'))
else:
from_simple_data(company_name, country_wise_tax)
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): def from_detailed_data(company_name, data):
""" """Create Taxes and Charges Templates from detailed 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') coa_name = frappe.db.get_value('Company', company_name, 'chart_of_accounts')
tax_templates = data.get(coa_name) or data.get('*') tax_templates = data.get(coa_name) or data.get('*')
sales_tax_templates = tax_templates.get('sales_tax_templates') or tax_templates.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: if sales_tax_templates:
for template in 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: if purchase_tax_templates:
for template in 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: if item_tax_templates:
for template in item_tax_templates: for template in item_tax_templates:
make_item_tax_template(company_name, template) make_item_tax_template(company_name, template)
def from_simple_data(company_name, data): def make_taxes_and_charges_template(company_name, doctype, template):
""" template['company'] = company_name
Create Taxes and Charges Templates from simple data like this: 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}): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
return return
accounts = get_or_create_accounts(company_name, template.get('accounts')) for tax_row in template.get('taxes'):
account_data = tax_row.get('account_head')
# Get all fields of the Taxes and Charges Template tax_row_defaults = {
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 = {
'category': 'Total', 'category': 'Total',
'charge_type': 'On Net 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)
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): 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. differently from Sales and Purchase Tax Templates.
""" """
doctype = 'Item Tax Template' doctype = 'Item Tax Template'
template['company'] = company_name
template['doctype'] = doctype
if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}): if frappe.db.exists(doctype, {'title': template.get('title'), 'company': company_name}):
return 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 = { # if tax_type is a dict, search or create the account and get it's name
'doctype': doctype, if isinstance(account_data, dict):
'title': template.get('title'), account = get_or_create_account(company_name, account_data)
'company': company_name, tax_row['tax_type'] = account.name
'taxes': [{ if 'tax_rate' not in tax_row:
'tax_type': account.get('name'), tax_row['tax_rate'] = account_data.get('tax_rate')
'tax_rate': account.get('tax_rate')
} for account in accounts]
}
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): def get_or_create_account(company_name, account):
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):
""" """
Check if account already exists. If not, create it. Check if account already exists. If not, create it.
Return a tax account or None. Return a tax account or None.
""" """
root_type = account_data.get('root_type', 'Liability') default_root_type = 'Liability'
account_name = account_data.get('account_name') root_type = account.get('root_type', default_root_type)
account_number = account_data.get('account_number')
existing_accounts = frappe.get_list('Account', existing_accounts = frappe.get_list('Account',
filters={ filters={
'company': company, 'company': company_name,
'root_type': root_type 'root_type': root_type
}, },
or_filters={ or_filters={
'account_name': account_name, 'account_name': account.get('account_name'),
'account_number': account_number 'account_number': account.get('account_number')
} }
) )
if existing_accounts: if existing_accounts:
return frappe.get_doc('Account', existing_accounts[0].name) return frappe.get_doc('Account', existing_accounts[0].name)
tax_group = get_or_create_tax_account_group(company, root_type) tax_group = get_or_create_tax_group(company_name, root_type)
full_account_data = {
'doctype': 'Account', account['doctype'] = 'Account'
'account_name': account_name, account['company'] = company_name
'account_number': account_number, account['parent_account'] = tax_group
'tax_rate': account_data.get('tax_rate'), account['report_type'] = 'Balance Sheet'
'company': company, account['account_type'] = 'Tax'
'parent_account': tax_group, account['root_type'] = root_type
'is_group': 0, account['is_group'] = 0
'report_type': 'Balance Sheet',
'root_type': root_type, return frappe.get_doc(account).insert(ignore_permissions=True, ignore_mandatory=True)
'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): def get_or_create_tax_group(company_name, root_type):
tax_group = frappe.db.get_value('Account', { # Look for a group account of type 'Tax'
tax_group_name = frappe.db.get_value('Account', {
'is_group': 1, 'is_group': 1,
'root_type': root_type, 'root_type': root_type,
'account_type': 'Tax', 'account_type': 'Tax',
'company': company 'company': company_name
}) })
if tax_group: if tax_group_name:
return tax_group 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, 'is_group': 1,
'root_type': root_type, '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', 'report_type': 'Balance Sheet',
'parent_account': ('is', 'not set') 'parent_account': ('is', 'not set')
}, limit=1)[0].name }, limit=1)[0]
doc = frappe.get_doc({ tax_group_account = frappe.get_doc({
'doctype': 'Account', 'doctype': 'Account',
'company': company, 'company': company_name,
'is_group': 1, 'is_group': 1,
'report_type': 'Balance Sheet', 'report_type': 'Balance Sheet',
'root_type': root_type, 'root_type': root_type,
'account_type': 'Tax', 'account_type': 'Tax',
'account_name': _('Duties and Taxes') if root_type == 'Liability' else _('Tax Assets'), 'account_name': account_name,
'parent_account': root 'parent_account': root_account.name
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True)
tax_group = doc.name tax_group_name = tax_group_account.name
return tax_group return tax_group_name