mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 03:01:22 +00:00
feat: add support for 'not applicable' tax in item tax templates (#50898)
* feat: add support for 'not applicable' tax in item tax templates * refactor: remove unused imports * fix: import NOT_APPLICABLE_TAX in get_item_tax_map function * fix: add item wise tax details for not applicable taxes * test: added test case for `not_applicable` * fix: do not create item wise tax details for not applicable tax * fix: ensure tax rate is set to 0 for not applicable tax rows * refactor: changes as per review * test: update selling settings * test: correct settings * fix: return both net and current tax amounts for not applicable tax
This commit is contained in:
@@ -69,6 +69,7 @@ from erpnext.setup.utils import get_exchange_rate
|
||||
from erpnext.stock.doctype.item.item import get_uom_conv_factor
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
from erpnext.stock.get_item_details import (
|
||||
NOT_APPLICABLE_TAX,
|
||||
ItemDetailsCtx,
|
||||
_get_item_tax_template,
|
||||
get_conversion_factor,
|
||||
@@ -3696,8 +3697,11 @@ def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
|
||||
|
||||
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
|
||||
tax_map = json.loads(child_item.get("item_tax_rate"))
|
||||
for tax_type in tax_map:
|
||||
tax_rate = flt(tax_map[tax_type])
|
||||
for tax_type, tax_rate in tax_map.items():
|
||||
if tax_rate == NOT_APPLICABLE_TAX:
|
||||
continue
|
||||
|
||||
tax_rate = flt(tax_rate)
|
||||
taxes = parent_doc.get("taxes") or []
|
||||
# add new row for tax head only if missing
|
||||
found = any(tax.account_head == tax_type for tax in taxes)
|
||||
|
||||
@@ -18,7 +18,11 @@ from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||
from erpnext.stock.get_item_details import get_conversion_factor, get_item_defaults
|
||||
from erpnext.stock.get_item_details import (
|
||||
NOT_APPLICABLE_TAX,
|
||||
get_conversion_factor,
|
||||
get_item_defaults,
|
||||
)
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
@@ -523,6 +527,9 @@ class BuyingController(SubcontractingController):
|
||||
if account not in tax_accounts:
|
||||
continue
|
||||
|
||||
if rate == NOT_APPLICABLE_TAX:
|
||||
continue
|
||||
|
||||
net_rate = item.base_net_amount
|
||||
if item.sales_incoming_rate:
|
||||
net_rate = item.qty * item.sales_incoming_rate
|
||||
|
||||
@@ -19,7 +19,12 @@ from erpnext.controllers.accounts_controller import (
|
||||
validate_taxes_and_charges,
|
||||
)
|
||||
from erpnext.deprecation_dumpster import deprecated
|
||||
from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template, get_item_tax_map
|
||||
from erpnext.stock.get_item_details import (
|
||||
NOT_APPLICABLE_TAX,
|
||||
ItemDetailsCtx,
|
||||
_get_item_tax_template,
|
||||
get_item_tax_map,
|
||||
)
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
|
||||
@@ -358,6 +363,9 @@ class calculate_taxes_and_totals:
|
||||
if cint(tax.included_in_print_rate):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
|
||||
if tax_rate == NOT_APPLICABLE_TAX:
|
||||
return current_tax_fraction, inclusive_tax_amount_per_qty
|
||||
|
||||
if tax.charge_type == "On Net Total":
|
||||
current_tax_fraction = tax_rate / 100.0
|
||||
|
||||
@@ -382,9 +390,12 @@ class calculate_taxes_and_totals:
|
||||
|
||||
def _get_tax_rate(self, tax, item_tax_map):
|
||||
if tax.account_head in item_tax_map:
|
||||
return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax))
|
||||
else:
|
||||
return tax.rate
|
||||
rate = item_tax_map[tax.account_head]
|
||||
if rate == NOT_APPLICABLE_TAX:
|
||||
return NOT_APPLICABLE_TAX
|
||||
return flt(rate, self.doc.precision("rate", tax))
|
||||
|
||||
return tax.rate
|
||||
|
||||
def calculate_net_total(self):
|
||||
self.doc.total_qty = (
|
||||
@@ -594,6 +605,9 @@ class calculate_taxes_and_totals:
|
||||
current_tax_amount = 0.0
|
||||
current_net_amount = 0.0
|
||||
|
||||
if tax_rate == NOT_APPLICABLE_TAX:
|
||||
return current_net_amount, current_tax_amount
|
||||
|
||||
if tax.charge_type == "Actual":
|
||||
current_net_amount = item.net_amount
|
||||
# distribute the tax amount proportionally to each item row
|
||||
|
||||
@@ -299,3 +299,238 @@ class TestTaxesAndTotals(ERPNextTestSuite):
|
||||
tax = doc.taxes[0]
|
||||
detail = doc.item_wise_tax_details[0]
|
||||
self.assertEqual(detail.amount, tax.base_tax_amount_after_discount_amount)
|
||||
|
||||
@change_settings("Selling Settings", {"allow_multiple_items": 1})
|
||||
def test_not_applicable_tax_in_item_tax_template(self):
|
||||
"""Test that items with 'not applicable' tax don't contribute to net amount of that tax."""
|
||||
template_7pct = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Tax Template",
|
||||
"title": "_Test VAT 7% Template",
|
||||
"company": "_Test Company",
|
||||
"taxes": [
|
||||
{
|
||||
"tax_type": "_Test Account VAT - _TC",
|
||||
"tax_rate": 7,
|
||||
},
|
||||
{
|
||||
"tax_type": "_Test Account Service Tax - _TC",
|
||||
"tax_rate": 0,
|
||||
"not_applicable": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
template_19pct = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Tax Template",
|
||||
"title": "_Test VAT 19% Template",
|
||||
"company": "_Test Company",
|
||||
"taxes": [
|
||||
{
|
||||
"tax_type": "_Test Account VAT - _TC",
|
||||
"tax_rate": 0,
|
||||
},
|
||||
{
|
||||
"tax_type": "_Test Account Service Tax - _TC",
|
||||
"tax_rate": 19,
|
||||
},
|
||||
],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
self.doc.items[0].item_tax_template = template_7pct.name
|
||||
|
||||
self.doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"item_tax_template": template_19pct.name,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT 7%",
|
||||
"rate": 7,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT 19%",
|
||||
"rate": 19,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.save()
|
||||
|
||||
# VAT 7%: Both items contribute (Item 2 has 0% rate, not "not applicable")
|
||||
self.assertEqual(self.doc.taxes[0].net_amount, 200.0)
|
||||
# Service Tax 19%: Only Item 2 contributes (Item 1 has not_applicable)
|
||||
self.assertEqual(self.doc.taxes[1].net_amount, 100.0)
|
||||
|
||||
expected_values = [
|
||||
{
|
||||
"item_row": self.doc.items[0].name,
|
||||
"tax_row": self.doc.taxes[0].name,
|
||||
"rate": 7.0,
|
||||
"amount": 7.0,
|
||||
"taxable_amount": 100.0,
|
||||
},
|
||||
{
|
||||
"item_row": self.doc.items[1].name,
|
||||
"tax_row": self.doc.taxes[0].name,
|
||||
"rate": 0.0,
|
||||
"amount": 0.0,
|
||||
"taxable_amount": 100.0,
|
||||
},
|
||||
{
|
||||
"item_row": self.doc.items[1].name,
|
||||
"tax_row": self.doc.taxes[1].name,
|
||||
"rate": 19.0,
|
||||
"amount": 19.0,
|
||||
"taxable_amount": 100.0,
|
||||
},
|
||||
]
|
||||
|
||||
actual_values = [
|
||||
{
|
||||
"item_row": row.item_row,
|
||||
"tax_row": row.tax_row,
|
||||
"rate": row.rate,
|
||||
"amount": row.amount,
|
||||
"taxable_amount": row.taxable_amount,
|
||||
}
|
||||
for row in self.doc.item_wise_tax_details
|
||||
]
|
||||
|
||||
self.assertEqual(actual_values, expected_values)
|
||||
|
||||
def test_not_applicable_tax_in_item_tax_template_with_different_items(self):
|
||||
"""Test that items with 'not applicable' tax don't contribute to net amount of that tax."""
|
||||
template_7pct = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Tax Template",
|
||||
"title": "_Test VAT 7% Template",
|
||||
"company": "_Test Company",
|
||||
"taxes": [
|
||||
{
|
||||
"tax_type": "_Test Account VAT - _TC",
|
||||
"tax_rate": 7,
|
||||
},
|
||||
{
|
||||
"tax_type": "_Test Account Service Tax - _TC",
|
||||
"tax_rate": 0,
|
||||
"not_applicable": 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
template_19pct = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Tax Template",
|
||||
"title": "_Test VAT 19% Template",
|
||||
"company": "_Test Company",
|
||||
"taxes": [
|
||||
{
|
||||
"tax_type": "_Test Account VAT - _TC",
|
||||
"tax_rate": 0,
|
||||
"not_applicable": 1,
|
||||
},
|
||||
{
|
||||
"tax_type": "_Test Account Service Tax - _TC",
|
||||
"tax_rate": 19,
|
||||
},
|
||||
],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
self.doc.items[0].item_tax_template = template_7pct.name
|
||||
|
||||
self.doc.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": "_Test Item 2",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"item_tax_template": template_19pct.name,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT 7%",
|
||||
"rate": 0,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT 19%",
|
||||
"rate": 0,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.save()
|
||||
|
||||
# VAT 7%: Only Item 1 contributes (Item 2 has not_applicable)
|
||||
self.assertEqual(self.doc.taxes[0].net_amount, 100.0)
|
||||
# Service Tax 19%: Only Item 2 contributes (Item 1 has not_applicable)
|
||||
self.assertEqual(self.doc.taxes[1].net_amount, 100.0)
|
||||
|
||||
expected_values = [
|
||||
{
|
||||
"item_row": self.doc.items[0].name,
|
||||
"tax_row": self.doc.taxes[0].name,
|
||||
"rate": 7.0,
|
||||
"amount": 7.0,
|
||||
"taxable_amount": 100.0,
|
||||
},
|
||||
{
|
||||
"item_row": self.doc.items[1].name,
|
||||
"tax_row": self.doc.taxes[1].name,
|
||||
"rate": 19.0,
|
||||
"amount": 19.0,
|
||||
"taxable_amount": 100.0,
|
||||
},
|
||||
]
|
||||
|
||||
actual_values = [
|
||||
{
|
||||
"item_row": row.item_row,
|
||||
"tax_row": row.tax_row,
|
||||
"rate": row.rate,
|
||||
"amount": row.amount,
|
||||
"taxable_amount": row.taxable_amount,
|
||||
}
|
||||
for row in self.doc.item_wise_tax_details
|
||||
]
|
||||
|
||||
self.assertEqual(actual_values, expected_values)
|
||||
|
||||
Reference in New Issue
Block a user