brotherton-erpnext/erpnext/setup/setup_wizard/operations/taxes_setup.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

301 lines
8.7 KiB
Python
Raw Normal View History

# 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):
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))
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)
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)
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()
2022-03-28 13:22:46 +00:00
],
"*": [
{
"title": title,
"is_default": data.get("default", 0),
"taxes": [
{
"account_head": {
"account_name": data.get("account_name"),
"tax_rate": data.get("tax_rate"),
2022-03-28 13:22:46 +00:00
}
}
],
}
for title, data in templates.items()
2022-03-28 13:22:46 +00:00
],
}
}
}
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")
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("*", {})
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)
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)
2022-03-28 13:22:46 +00:00
)
2021-06-02 07:56:21 +00:00
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("Unable to setup regional tax settings")
2021-06-02 07:56:21 +00:00
pass
2022-03-28 13:22:46 +00:00
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")
2022-03-28 13:22:46 +00:00
)
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
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.flags.ignore_mandatory = True
2021-04-30 11:05:52 +00:00
doc.insert(ignore_permissions=True)
return doc
2022-03-28 13:22:46 +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"
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")
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
2022-03-28 13:22:46 +00:00
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",
2021-06-26 18:19:32 +00:00
filters={"company": company_name, "root_type": root_type},
or_filters={
2021-06-26 18:19:32 +00:00
"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
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
2022-03-28 13:22:46 +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",
{"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]
2022-03-28 13:22:46 +00:00
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,
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)
tax_group_name = tax_group_account.name
return tax_group_name
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)