mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-21 22:19:18 +00:00
Merge pull request #55127 from diptanilsaha/fix/tax-rule-date-filter
refactor: migrate get_tax_template to query builder with hierarchical group matching
This commit is contained in:
@@ -14,6 +14,7 @@ from frappe.utils import cstr
|
|||||||
from frappe.utils.nestedset import get_root_of
|
from frappe.utils.nestedset import get_root_of
|
||||||
|
|
||||||
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
|
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
|
||||||
|
from erpnext.setup.doctype.supplier_group.supplier_group import get_parent_supplier_groups
|
||||||
|
|
||||||
|
|
||||||
class IncorrectCustomerGroup(frappe.ValidationError):
|
class IncorrectCustomerGroup(frappe.ValidationError):
|
||||||
@@ -176,38 +177,44 @@ def get_party_details(party: str | None, party_type: str, args: dict | None = No
|
|||||||
def get_tax_template(posting_date, args):
|
def get_tax_template(posting_date, args):
|
||||||
"""Get matching tax rule"""
|
"""Get matching tax rule"""
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
conditions = []
|
|
||||||
|
TaxRule = DocType("Tax Rule")
|
||||||
|
query = frappe.qb.from_(TaxRule).select("*")
|
||||||
|
|
||||||
if posting_date:
|
if posting_date:
|
||||||
conditions.append(
|
query = query.where(
|
||||||
f"""(from_date is null or from_date <= '{posting_date}')
|
(TaxRule.from_date.isnull() | (TaxRule.from_date <= posting_date))
|
||||||
and (to_date is null or to_date >= '{posting_date}')"""
|
& (TaxRule.to_date.isnull() | (TaxRule.to_date >= posting_date))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conditions.append("(from_date is null) and (to_date is null)")
|
query = query.where(TaxRule.from_date.isnull() & TaxRule.to_date.isnull())
|
||||||
|
|
||||||
conditions.append(
|
def get_group_ancestors(doctype, get_parents, value):
|
||||||
"ifnull(tax_category, '') = {}".format(frappe.db.escape(cstr(args.get("tax_category")), False))
|
if not value:
|
||||||
)
|
value = get_root_of(doctype)
|
||||||
if "tax_category" in args.keys():
|
return [""] + [d.name for d in get_parents(value)]
|
||||||
del args["tax_category"]
|
|
||||||
|
group_fields = {
|
||||||
|
"customer_group": ("Customer Group", get_parent_customer_groups),
|
||||||
|
"supplier_group": ("Supplier Group", get_parent_supplier_groups),
|
||||||
|
}
|
||||||
|
|
||||||
|
args.setdefault("tax_category", "")
|
||||||
|
|
||||||
for key, value in args.items():
|
for key, value in args.items():
|
||||||
if key == "use_for_shopping_cart":
|
if key == "use_for_shopping_cart":
|
||||||
conditions.append(f"use_for_shopping_cart = {1 if value else 0}")
|
query = query.where(TaxRule.use_for_shopping_cart == value)
|
||||||
elif key == "customer_group":
|
elif key == "tax_category":
|
||||||
if not value:
|
query = query.where(IfNull(TaxRule.tax_category, "") == (value or ""))
|
||||||
value = get_root_of("Customer Group")
|
elif key in group_fields:
|
||||||
customer_group_condition = get_customer_group_condition(value)
|
doctype, get_parents = group_fields[key]
|
||||||
conditions.append(f"ifnull({key}, '') in ('', {customer_group_condition})")
|
query = query.where(
|
||||||
|
IfNull(TaxRule[key], "").isin(get_group_ancestors(doctype, get_parents, value))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
conditions.append(f"ifnull({key}, '') in ('', {frappe.db.escape(cstr(value))})")
|
query = query.where(IfNull(TaxRule[key], "").isin(["", value or ""]))
|
||||||
|
|
||||||
tax_rule = frappe.db.sql(
|
tax_rule = query.run(as_dict=True)
|
||||||
"""select * from `tabTax Rule`
|
|
||||||
where {}""".format(" and ".join(conditions)),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not tax_rule:
|
if not tax_rule:
|
||||||
return None
|
return None
|
||||||
@@ -236,11 +243,3 @@ def get_tax_template(posting_date, args):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return tax_template
|
return tax_template
|
||||||
|
|
||||||
|
|
||||||
def get_customer_group_condition(customer_group):
|
|
||||||
condition = ""
|
|
||||||
customer_groups = ["%s" % (frappe.db.escape(d.name)) for d in get_parent_customer_groups(customer_group)]
|
|
||||||
if customer_groups:
|
|
||||||
condition = ",".join(["%s"] * len(customer_groups)) % (tuple(customer_groups))
|
|
||||||
return condition
|
|
||||||
|
|||||||
@@ -61,6 +61,117 @@ class TestTaxRule(ERPNextTestSuite):
|
|||||||
"_Test Sales Taxes and Charges Template - _TC",
|
"_Test Sales Taxes and Charges Template - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_for_parent_supplier_group(self):
|
||||||
|
purchase_template = "_Test Purchase Taxes and Charges Template - _TC"
|
||||||
|
if not frappe.db.exists("Purchase Taxes and Charges Template", purchase_template):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Purchase Taxes and Charges Template",
|
||||||
|
"title": "_Test Purchase Taxes and Charges Template",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"taxes": [
|
||||||
|
{
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Purchase Taxes and Charges",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"rate": 6,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
make_tax_rule(
|
||||||
|
supplier_group="All Supplier Groups",
|
||||||
|
tax_type="Purchase",
|
||||||
|
purchase_tax_template=purchase_template,
|
||||||
|
priority=1,
|
||||||
|
use_for_shopping_cart=0,
|
||||||
|
from_date="2015-01-01",
|
||||||
|
save=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# "_Test Supplier Group" has "All Supplier Groups" as its parent — should match hierarchically
|
||||||
|
self.assertEqual(
|
||||||
|
get_tax_template(
|
||||||
|
"2015-01-01",
|
||||||
|
{
|
||||||
|
"supplier_group": "_Test Supplier Group",
|
||||||
|
"tax_type": "Purchase",
|
||||||
|
"use_for_shopping_cart": 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
purchase_template,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_use_for_shopping_cart_filter(self):
|
||||||
|
city = "Test Cart City"
|
||||||
|
# higher priority ensures this rule wins when use_for_shopping_cart is not filtered
|
||||||
|
make_tax_rule(
|
||||||
|
customer="_Test Customer",
|
||||||
|
billing_city=city,
|
||||||
|
sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
|
||||||
|
use_for_shopping_cart=0,
|
||||||
|
priority=2,
|
||||||
|
save=1,
|
||||||
|
)
|
||||||
|
make_tax_rule(
|
||||||
|
customer="_Test Customer",
|
||||||
|
billing_city=city,
|
||||||
|
sales_tax_template="_Test Sales Taxes and Charges Template 1 - _TC",
|
||||||
|
use_for_shopping_cart=1,
|
||||||
|
priority=1,
|
||||||
|
save=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cart request (use_for_shopping_cart=1) filters to cart rules only
|
||||||
|
self.assertEqual(
|
||||||
|
get_tax_template(
|
||||||
|
"2015-01-01",
|
||||||
|
{"customer": "_Test Customer", "billing_city": city, "use_for_shopping_cart": 1},
|
||||||
|
),
|
||||||
|
"_Test Sales Taxes and Charges Template 1 - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Non-cart request omits use_for_shopping_cart — no filter is applied, both rules
|
||||||
|
# are candidates; non-cart rule wins by higher priority
|
||||||
|
self.assertEqual(
|
||||||
|
get_tax_template(
|
||||||
|
"2015-01-01",
|
||||||
|
{"customer": "_Test Customer", "billing_city": city},
|
||||||
|
),
|
||||||
|
"_Test Sales Taxes and Charges Template - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_use_for_shopping_cart_default(self):
|
||||||
|
city = "Test Default Cart City"
|
||||||
|
# use_for_shopping_cart not set — Check field defaults to 0
|
||||||
|
make_tax_rule(
|
||||||
|
customer="_Test Customer",
|
||||||
|
billing_city=city,
|
||||||
|
sales_tax_template="_Test Sales Taxes and Charges Template - _TC",
|
||||||
|
use_for_shopping_cart=0, # Default is set to 1.
|
||||||
|
save=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Non-cart request (no use_for_shopping_cart in args) matches the rule
|
||||||
|
self.assertEqual(
|
||||||
|
get_tax_template(
|
||||||
|
"2015-01-01",
|
||||||
|
{"customer": "_Test Customer", "billing_city": city},
|
||||||
|
),
|
||||||
|
"_Test Sales Taxes and Charges Template - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Cart request (use_for_shopping_cart=1) does not match — rule has default 0
|
||||||
|
self.assertIsNone(
|
||||||
|
get_tax_template(
|
||||||
|
"2015-01-01",
|
||||||
|
{"customer": "_Test Customer", "billing_city": city, "use_for_shopping_cart": 1},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_conflict_with_overlapping_dates(self):
|
def test_conflict_with_overlapping_dates(self):
|
||||||
tax_rule1 = make_tax_rule(
|
tax_rule1 = make_tax_rule(
|
||||||
customer="_Test Customer",
|
customer="_Test Customer",
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ def set_taxes(
|
|||||||
args.update({"tax_type": "Purchase"})
|
args.update({"tax_type": "Purchase"})
|
||||||
|
|
||||||
if use_for_shopping_cart:
|
if use_for_shopping_cart:
|
||||||
args.update({"use_for_shopping_cart": use_for_shopping_cart})
|
args.update({"use_for_shopping_cart": cint(use_for_shopping_cart)})
|
||||||
|
|
||||||
return get_tax_template(posting_date, args)
|
return get_tax_template(posting_date, args)
|
||||||
|
|
||||||
|
|||||||
@@ -75,13 +75,11 @@ class CustomerGroup(NestedSet):
|
|||||||
|
|
||||||
def get_parent_customer_groups(customer_group):
|
def get_parent_customer_groups(customer_group):
|
||||||
lft, rgt = frappe.db.get_value("Customer Group", customer_group, ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Customer Group", customer_group, ["lft", "rgt"])
|
||||||
|
return frappe.get_all(
|
||||||
return frappe.db.sql(
|
"Customer Group",
|
||||||
"""select name from `tabCustomer Group`
|
filters=[["lft", "<=", lft], ["rgt", ">=", rgt]],
|
||||||
where lft <= %s and rgt >= %s
|
fields=["name"],
|
||||||
order by lft asc""",
|
order_by="lft asc",
|
||||||
(lft, rgt),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -70,3 +70,13 @@ class SupplierGroup(NestedSet):
|
|||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
NestedSet.validate_if_child_exists(self)
|
NestedSet.validate_if_child_exists(self)
|
||||||
frappe.utils.nestedset.update_nsm(self)
|
frappe.utils.nestedset.update_nsm(self)
|
||||||
|
|
||||||
|
|
||||||
|
def get_parent_supplier_groups(supplier_group):
|
||||||
|
lft, rgt = frappe.db.get_value("Supplier Group", supplier_group, ["lft", "rgt"])
|
||||||
|
return frappe.get_all(
|
||||||
|
"Supplier Group",
|
||||||
|
filters=[["lft", "<=", lft], ["rgt", ">=", rgt]],
|
||||||
|
fields=["name"],
|
||||||
|
order_by="lft asc",
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user