mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-27 02:28:30 +00:00
feat: enhance tax withholding details report with additional columns support (backport #54409) (#54432)
This commit is contained in:
@@ -11,6 +11,10 @@ from erpnext.accounts.utils import get_currency_precision
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
return _execute(filters)
|
||||||
|
|
||||||
|
|
||||||
|
def _execute(filters=None, additional_table_columns=None):
|
||||||
if filters.get("party_type") == "Customer":
|
if filters.get("party_type") == "Customer":
|
||||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||||
else:
|
else:
|
||||||
@@ -25,9 +29,9 @@ def execute(filters=None):
|
|||||||
net_total_map,
|
net_total_map,
|
||||||
) = get_tds_docs(filters)
|
) = get_tds_docs(filters)
|
||||||
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters, additional_table_columns)
|
||||||
|
|
||||||
res = get_result(filters, tds_accounts, tax_category_map, net_total_map)
|
res = get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns)
|
||||||
return columns, res
|
return columns, res
|
||||||
|
|
||||||
|
|
||||||
@@ -38,12 +42,14 @@ def validate_filters(filters):
|
|||||||
frappe.throw(_("From Date must be before To Date"))
|
frappe.throw(_("From Date must be before To Date"))
|
||||||
|
|
||||||
|
|
||||||
def get_result(filters, tds_accounts, tax_category_map, net_total_map):
|
def get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns=None):
|
||||||
party_names = {v.party for v in net_total_map.values() if v.party}
|
party_names = {v.party for v in net_total_map.values() if v.party}
|
||||||
party_map = get_party_pan_map(filters.get("party_type"), party_names)
|
party_map = get_party_pan_map(filters.get("party_type"), party_names)
|
||||||
tax_rate_map = get_tax_rate_map(filters)
|
tax_rate_map = get_tax_rate_map(filters)
|
||||||
gle_map = get_gle_map(net_total_map)
|
gle_map = get_gle_map(net_total_map)
|
||||||
precision = get_currency_precision()
|
precision = get_currency_precision()
|
||||||
|
twc = get_tax_withholding_category_details(additional_table_columns)
|
||||||
|
twc_additional_columns = _get_twc_additional_columns(additional_table_columns)
|
||||||
|
|
||||||
entries = {}
|
entries = {}
|
||||||
for (voucher_type, name), details in gle_map.items():
|
for (voucher_type, name), details in gle_map.items():
|
||||||
@@ -119,8 +125,8 @@ def get_result(filters, tds_accounts, tax_category_map, net_total_map):
|
|||||||
|
|
||||||
row.update(
|
row.update(
|
||||||
{
|
{
|
||||||
"section_code": tax_withholding_category or "",
|
"tax_withholding_category": tax_withholding_category or "",
|
||||||
"entity_type": party_map.get(party, {}).get(party_type),
|
"party_entity_type": party_map.get(party, {}).get(party_type),
|
||||||
"rate": rate,
|
"rate": rate,
|
||||||
"total_amount": total_amount,
|
"total_amount": total_amount,
|
||||||
"grand_total": grand_total,
|
"grand_total": grand_total,
|
||||||
@@ -135,17 +141,47 @@ def get_result(filters, tds_accounts, tax_category_map, net_total_map):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if tax_withholding_category:
|
||||||
|
if twc_details := twc.get(tax_withholding_category, {}):
|
||||||
|
for col in twc_additional_columns or []:
|
||||||
|
row[col] = twc_details.get(col)
|
||||||
|
|
||||||
key = entry.voucher_no
|
key = entry.voucher_no
|
||||||
if key in entries:
|
if key in entries:
|
||||||
entries[key]["tax_amount"] += tax_amount
|
entries[key]["tax_amount"] += tax_amount
|
||||||
else:
|
else:
|
||||||
entries[key] = row
|
entries[key] = row
|
||||||
out = list(entries.values())
|
out = list(entries.values())
|
||||||
out.sort(key=lambda x: (x["section_code"], x["transaction_date"], x["ref_no"]))
|
out.sort(key=lambda x: (x["tax_withholding_category"], x["transaction_date"], x["ref_no"]))
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def get_tax_withholding_category_details(additional_table_columns=None):
|
||||||
|
if not additional_table_columns:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
category_fields = _get_twc_additional_columns(additional_table_columns)
|
||||||
|
|
||||||
|
if not category_fields:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
rows = frappe.get_all("Tax Withholding Category", fields=["name", *category_fields])
|
||||||
|
|
||||||
|
return {row["name"]: row for row in rows}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_twc_additional_columns(additional_table_columns):
|
||||||
|
if not additional_table_columns:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
col.get("fieldname")
|
||||||
|
for col in additional_table_columns
|
||||||
|
if col.get("_doctype") == "Tax Withholding Category" and col.get("fieldname")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_party_pan_map(party_type, party_names):
|
def get_party_pan_map(party_type, party_names):
|
||||||
party_map = frappe._dict()
|
party_map = frappe._dict()
|
||||||
|
|
||||||
@@ -201,19 +237,22 @@ def get_gle_map(net_total_map):
|
|||||||
return gle_map
|
return gle_map
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters, additional_table_columns=None):
|
||||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
"label": _("Section Code"),
|
"label": _("Tax Withholding Category"),
|
||||||
"options": "Tax Withholding Category",
|
"options": "Tax Withholding Category",
|
||||||
"fieldname": "section_code",
|
"fieldname": "tax_withholding_category",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"width": 90,
|
"width": 180,
|
||||||
},
|
},
|
||||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if additional_table_columns:
|
||||||
|
columns.extend(additional_table_columns)
|
||||||
|
|
||||||
if filters.naming_series == "Naming Series":
|
if filters.naming_series == "Naming Series":
|
||||||
columns.append(
|
columns.append(
|
||||||
{
|
{
|
||||||
@@ -236,7 +275,12 @@ def get_columns(filters):
|
|||||||
|
|
||||||
columns.extend(
|
columns.extend(
|
||||||
[
|
[
|
||||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
{
|
||||||
|
"label": _(f"{filters.get('party_type', 'Party')} Type"),
|
||||||
|
"fieldname": "party_entity_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if filters.party_type == "Supplier":
|
if filters.party_type == "Supplier":
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import (
|
from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import (
|
||||||
create_tax_withholding_category,
|
create_tax_withholding_category,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import execute
|
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import _execute, execute
|
||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
@@ -112,13 +112,49 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
|
|||||||
]
|
]
|
||||||
self.check_expected_values(result, expected_values)
|
self.check_expected_values(result, expected_values)
|
||||||
|
|
||||||
|
def test_additional_tax_withholding_category_column(self):
|
||||||
|
tds_category = "TDS - Additional Column"
|
||||||
|
create_tax_category(tds_category, rate=10, account="TDS - _TC", cumulative_threshold=1)
|
||||||
|
inv = make_purchase_invoice(rate=1000, do_not_submit=True)
|
||||||
|
inv.apply_tds = 1
|
||||||
|
inv.tax_withholding_category = tds_category
|
||||||
|
inv.submit()
|
||||||
|
|
||||||
|
field_name = "category_name"
|
||||||
|
expected_value = "Additional Column Display Name"
|
||||||
|
frappe.db.set_value("Tax Withholding Category", tds_category, field_name, expected_value)
|
||||||
|
|
||||||
|
additional_table_columns = [
|
||||||
|
{
|
||||||
|
"label": "Category Name",
|
||||||
|
"fieldname": field_name,
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 140,
|
||||||
|
"_doctype": "Tax Withholding Category",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
filters = frappe._dict(
|
||||||
|
company="_Test Company",
|
||||||
|
party_type="Supplier",
|
||||||
|
from_date=today(),
|
||||||
|
to_date=today(),
|
||||||
|
)
|
||||||
|
|
||||||
|
columns, data = _execute(filters, additional_table_columns)
|
||||||
|
|
||||||
|
self.assertTrue(any(col.get("fieldname") == field_name for col in columns))
|
||||||
|
invoice_row = next((row for row in data if row.get("ref_no") == inv.name), None)
|
||||||
|
self.assertIsNotNone(invoice_row)
|
||||||
|
self.assertEqual(invoice_row.get(field_name), expected_value)
|
||||||
|
|
||||||
def check_expected_values(self, result, expected_values):
|
def check_expected_values(self, result, expected_values):
|
||||||
for i in range(len(result)):
|
for i in range(len(result)):
|
||||||
voucher = frappe._dict(result[i])
|
voucher = frappe._dict(result[i])
|
||||||
voucher_expected_values = expected_values[i]
|
voucher_expected_values = expected_values[i]
|
||||||
voucher_actual_values = (
|
voucher_actual_values = (
|
||||||
voucher.ref_no,
|
voucher.ref_no,
|
||||||
voucher.section_code,
|
voucher.tax_withholding_category,
|
||||||
voucher.rate,
|
voucher.rate,
|
||||||
voucher.base_tax_withholding_net_total,
|
voucher.base_tax_withholding_net_total,
|
||||||
voucher.base_total,
|
voucher.base_total,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import (
|
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import (
|
||||||
|
_get_twc_additional_columns,
|
||||||
get_result,
|
get_result,
|
||||||
get_tds_docs,
|
get_tds_docs,
|
||||||
)
|
)
|
||||||
@@ -9,6 +10,10 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
return _execute(filters)
|
||||||
|
|
||||||
|
|
||||||
|
def _execute(filters=None, additional_table_columns=None):
|
||||||
if filters.get("party_type") == "Customer":
|
if filters.get("party_type") == "Customer":
|
||||||
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||||
else:
|
else:
|
||||||
@@ -18,15 +23,15 @@ def execute(filters=None):
|
|||||||
|
|
||||||
validate_filters(filters)
|
validate_filters(filters)
|
||||||
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters, additional_table_columns)
|
||||||
(
|
(
|
||||||
tds_accounts,
|
tds_accounts,
|
||||||
tax_category_map,
|
tax_category_map,
|
||||||
net_total_map,
|
net_total_map,
|
||||||
) = get_tds_docs(filters)
|
) = get_tds_docs(filters)
|
||||||
|
|
||||||
res = get_result(filters, tds_accounts, tax_category_map, net_total_map)
|
res = get_result(filters, tds_accounts, tax_category_map, net_total_map, additional_table_columns)
|
||||||
final_result = group_by_party_and_category(res, filters)
|
final_result = group_by_party_and_category(res, filters, additional_table_columns)
|
||||||
|
|
||||||
return columns, final_result
|
return columns, final_result
|
||||||
|
|
||||||
@@ -44,32 +49,33 @@ def validate_filters(filters):
|
|||||||
filters["fiscal_year"] = from_year
|
filters["fiscal_year"] = from_year
|
||||||
|
|
||||||
|
|
||||||
def group_by_party_and_category(data, filters):
|
def group_by_party_and_category(data, filters, additional_table_columns=None):
|
||||||
party_category_wise_map = {}
|
party_category_wise_map = {}
|
||||||
|
twc_additional_columns = _get_twc_additional_columns(additional_table_columns)
|
||||||
|
|
||||||
for row in data:
|
for row in data:
|
||||||
party_category_wise_map.setdefault(
|
key = (row.get("party"), row.get("tax_withholding_category"))
|
||||||
(row.get("party"), row.get("section_code")),
|
default_row = {
|
||||||
{
|
"pan": row.get("pan"),
|
||||||
"pan": row.get("pan"),
|
"tax_id": row.get("tax_id"),
|
||||||
"tax_id": row.get("tax_id"),
|
"party": row.get("party"),
|
||||||
"party": row.get("party"),
|
"party_name": row.get("party_name"),
|
||||||
"party_name": row.get("party_name"),
|
"tax_withholding_category": row.get("tax_withholding_category"),
|
||||||
"section_code": row.get("section_code"),
|
"party_entity_type": row.get("party_entity_type"),
|
||||||
"entity_type": row.get("entity_type"),
|
"rate": row.get("rate"),
|
||||||
"rate": row.get("rate"),
|
"total_amount": 0.0,
|
||||||
"total_amount": 0.0,
|
"tax_amount": 0.0,
|
||||||
"tax_amount": 0.0,
|
}
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))["total_amount"] += row.get(
|
if twc_additional_columns:
|
||||||
"total_amount", 0.0
|
for col in twc_additional_columns:
|
||||||
)
|
default_row[col] = row.get(col)
|
||||||
|
|
||||||
party_category_wise_map.get((row.get("party"), row.get("section_code")))["tax_amount"] += row.get(
|
party_category_wise_map.setdefault(key, default_row)
|
||||||
"tax_amount", 0.0
|
|
||||||
)
|
party_category_wise_map.get(key)["total_amount"] += row.get("total_amount", 0.0)
|
||||||
|
|
||||||
|
party_category_wise_map.get(key)["tax_amount"] += row.get("tax_amount", 0.0)
|
||||||
|
|
||||||
final_result = get_final_result(party_category_wise_map)
|
final_result = get_final_result(party_category_wise_map)
|
||||||
|
|
||||||
@@ -84,7 +90,7 @@ def get_final_result(party_category_wise_map):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters, additional_table_columns=None):
|
||||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||||
columns = [
|
columns = [
|
||||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
|
||||||
@@ -107,16 +113,24 @@ def get_columns(filters):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if additional_table_columns:
|
||||||
|
columns.extend(additional_table_columns)
|
||||||
|
|
||||||
columns.extend(
|
columns.extend(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"label": _("Section Code"),
|
"label": _("Tax Withholding Category"),
|
||||||
"options": "Tax Withholding Category",
|
"options": "Tax Withholding Category",
|
||||||
"fieldname": "section_code",
|
"fieldname": "tax_withholding_category",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"width": 180,
|
"width": 180,
|
||||||
},
|
},
|
||||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
|
{
|
||||||
|
"label": _(f"{filters.get('party_type', 'Party')} Type"),
|
||||||
|
"fieldname": "party_entity_type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 180,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
|
|||||||
Reference in New Issue
Block a user