Merge pull request #32646 from niralisatapara/tds_purchase_invoice
feat: Item Wise TDS Calculation
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index e73d602..370c0fc 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -57,6 +57,8 @@
"column_break_28",
"total",
"net_total",
+ "tax_withholding_net_total",
+ "base_tax_withholding_net_total",
"taxes_section",
"taxes_and_charges",
"column_break_58",
@@ -89,7 +91,6 @@
"section_break_44",
"apply_discount_on",
"base_discount_amount",
- "additional_discount_account",
"column_break_46",
"additional_discount_percentage",
"discount_amount",
@@ -1422,6 +1423,26 @@
"read_only": 1
},
{
+ "default": "0",
+ "fieldname": "tax_withholding_net_total",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Tax Withholding Net Total",
+ "no_copy": 1,
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_tax_withholding_net_total",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Base Tax Withholding Net Total",
+ "no_copy": 1,
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
"collapsible_depends_on": "tax_withheld_vouchers",
"fieldname": "tax_withheld_vouchers_section",
"fieldtype": "Section Break",
@@ -1519,7 +1540,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2022-10-11 13:04:44.304389",
+ "modified": "2022-11-04 01:02:44.544878",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index a8f6f80..5c1cb0d 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -40,6 +40,7 @@
"discount_amount",
"base_rate_with_margin",
"sec_break2",
+ "apply_tds",
"rate",
"amount",
"item_tax_template",
@@ -868,6 +869,12 @@
"label": "Product Bundle",
"options": "Product Bundle",
"read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "apply_tds",
+ "fieldtype": "Check",
+ "label": "Apply TDS"
}
],
"idx": 1,
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 7eddd81..30ed91b 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -61,6 +61,9 @@
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
+ if inv.doctype == "Payment Entry":
+ inv.tax_withholding_net_total = inv.net_total
+
pan_no = ""
parties = []
party_type, party = get_party_details(inv)
@@ -242,7 +245,7 @@
if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted:
- net_total = inv.net_total
+ net_total = inv.tax_withholding_net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total
@@ -272,6 +275,11 @@
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
+ field = (
+ "base_tax_withholding_net_total as base_net_total"
+ if party_type == "Supplier"
+ else "base_net_total"
+ )
voucher_wise_amount = {}
vouchers = []
@@ -288,7 +296,7 @@
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
- invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])
+ invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field])
for d in invoices_details:
vouchers.append(d.name)
@@ -392,7 +400,7 @@
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
- field = "sum(net_total)"
+ field = "sum(tax_withholding_net_total)"
if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None)
@@ -415,12 +423,12 @@
)
supp_credit_amt += supp_jv_credit_amt
- supp_credit_amt += inv.net_total
+ supp_credit_amt += inv.tax_withholding_net_total
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
- if (threshold and inv.net_total >= threshold) or (
+ if (threshold and inv.tax_withholding_net_total >= threshold) or (
cumulative_threshold and supp_credit_amt >= cumulative_threshold
):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
@@ -428,11 +436,11 @@
):
# Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach
- net_total = 0
- if vouchers:
- net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
-
- net_total += inv.net_total
+ net_total = (
+ frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)")
+ or 0.0
+ )
+ net_total += inv.tax_withholding_net_total
supp_credit_amt = net_total - cumulative_threshold
if ldc and is_valid_certificate(
@@ -440,7 +448,7 @@
ldc.valid_upto,
inv.get("posting_date") or inv.get("transaction_date"),
tax_deducted,
- inv.net_total,
+ inv.tax_withholding_net_total,
ldc.certificate_limit,
):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
@@ -523,7 +531,7 @@
limit_consumed = frappe.db.get_value(
"Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
- "sum(net_total)",
+ "sum(tax_withholding_net_total)",
)
if is_valid_certificate(
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index e80fe11..40c732b 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -186,6 +186,46 @@
for d in reversed(invoices):
d.cancel()
+ def test_tds_calculation_on_net_total_partial_tds(self):
+ frappe.db.set_value(
+ "Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
+ )
+ invoices = []
+
+ pi = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
+ pi.extend(
+ "items",
+ [
+ {
+ "doctype": "Purchase Invoice Item",
+ "item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
+ "qty": 1,
+ "rate": 20000,
+ "cost_center": "Main - _TC",
+ "expense_account": "Stock Received But Not Billed - _TC",
+ "apply_tds": 0,
+ },
+ {
+ "doctype": "Purchase Invoice Item",
+ "item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
+ "qty": 1,
+ "rate": 35000,
+ "cost_center": "Main - _TC",
+ "expense_account": "Stock Received But Not Billed - _TC",
+ "apply_tds": 1,
+ },
+ ],
+ )
+ pi.save()
+ pi.submit()
+ invoices.append(pi)
+
+ self.assertEqual(pi.taxes[0].tax_amount, 5500)
+
+ # cancel invoices to avoid clashing
+ for d in reversed(invoices):
+ d.cancel()
+
def test_multi_category_single_supplier(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index b5836c9..81de682 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -58,12 +58,25 @@
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_net_total()
+ self.calculate_tax_withholding_net_total()
self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals()
self._cleanup()
self.calculate_total_net_weight()
+ def calculate_tax_withholding_net_total(self):
+ if hasattr(self.doc, "tax_withholding_net_total"):
+ sum_net_amount = 0
+ sum_base_net_amount = 0
+ for item in self.doc.get("items"):
+ if hasattr(item, "apply_tds") and item.apply_tds:
+ sum_net_amount += item.net_amount
+ sum_base_net_amount += item.base_net_amount
+
+ self.doc.tax_withholding_net_total = sum_net_amount
+ self.doc.base_tax_withholding_net_total = sum_base_net_amount
+
def validate_item_tax_template(self):
for item in self.doc.get("items"):
if item.item_code and item.get("item_tax_template"):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 6a8c21f..2624181 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -317,3 +317,4 @@
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
+erpnext.patches.v14_0.update_tds_fields
diff --git a/erpnext/patches/v14_0/update_tds_fields.py b/erpnext/patches/v14_0/update_tds_fields.py
new file mode 100644
index 0000000..a333c5d
--- /dev/null
+++ b/erpnext/patches/v14_0/update_tds_fields.py
@@ -0,0 +1,29 @@
+import frappe
+from frappe.utils import nowdate
+
+from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
+
+
+def execute():
+ # Only do for current fiscal year, no need to repost for all years
+ for company in frappe.get_all("Company"):
+ try:
+ fiscal_year_details = get_fiscal_year(date=nowdate(), company=company.name, as_dict=True)
+
+ purchase_invoice = frappe.qb.DocType("Purchase Invoice")
+
+ frappe.qb.update(purchase_invoice).set(
+ purchase_invoice.tax_withholding_net_total, purchase_invoice.net_total
+ ).set(
+ purchase_invoice.base_tax_withholding_net_total, purchase_invoice.base_net_total
+ ).where(
+ purchase_invoice.company == company.name
+ ).where(
+ purchase_invoice.apply_tds == 1
+ ).where(
+ purchase_invoice.posting_date >= fiscal_year_details.year_start_date
+ ).where(
+ purchase_invoice.docstatus == 1
+ ).run()
+ except FiscalYearError:
+ pass
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 677ca78..46ac808 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1207,7 +1207,7 @@
"base_rounding_adjustment"], company_currency);
this.frm.set_currency_labels(["total", "net_total", "total_taxes_and_charges", "discount_amount",
- "grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted",
+ "grand_total", "taxes_and_charges_added", "taxes_and_charges_deducted","tax_withholding_net_total",
"rounded_total", "in_words", "paid_amount", "write_off_amount", "operating_cost",
"scrap_material_cost", "rounding_adjustment", "raw_material_cost",
"total_cost"], this.frm.doc.currency);
@@ -1224,7 +1224,7 @@
}
// toggle fields
- this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total",
+ this.frm.toggle_display(["conversion_rate", "base_total", "base_net_total", "base_tax_withholding_net_total",
"base_total_taxes_and_charges", "base_taxes_and_charges_added", "base_taxes_and_charges_deducted",
"base_grand_total", "base_rounded_total", "base_in_words", "base_discount_amount",
"base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",