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 25b128b..2f9ee97 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",
@@ -1422,6 +1424,24 @@
"read_only": 1
},
{
+ "default": "0",
+ "fieldname": "tax_withholding_net_total",
+ "fieldtype": "Currency",
+ "label": "Tax Withholding Net Total",
+ "no_copy": 1,
+ "options": "currency",
+ "read_only": 1
+ },
+ {
+ "fieldname": "base_tax_withholding_net_total",
+ "fieldtype": "Currency",
+ "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",
@@ -1583,4 +1603,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 58e29f1..76ea955 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1574,35 +1574,6 @@
self.assertTrue(return_pi.docstatus == 1)
- def test_without_tds(self):
- make_purchase_invoice_tds()
-
- def test_total_tds(self):
- supplier = create_supplier(
- supplier_name="_Test TDS Advance Supplier",
- tax_withholding_category="TDS - 194 - Dividends - Individual",
- )
- pi = make_purchase_invoice_tds(supplier= "_Test TDS Advance Supplier",total_tds = 1)
-
- sum_tds = 0
- for item in pi.items:
- sum_tds += item.net_amount
-
- self.assertEqual(pi.tax_withholding_net_total, sum_tds)
- for tax in pi.taxes:
- self.assertEqual(tax.tax_amount, pi.tax_withholding_net_total * 0.10)
-
- def test_partial_tds(self):
- pi = make_purchase_invoice_tds(supplier= "_Test TDS Advance Supplier",partial_tds = 1)
-
- sum_tds = 0
- for item in pi.items:
- if item.apply_tds:
- sum_tds += item.net_amount
-
- self.assertEqual(pi.tax_withholding_net_total, sum_tds)
- for tax in pi.taxes:
- self.assertEqual(tax.tax_amount, pi.tax_withholding_net_total * 0.10)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(
@@ -1711,86 +1682,6 @@
pi.submit()
return pi
-def make_purchase_invoice_tds(**args):
- pi = frappe.new_doc("Purchase Invoice")
- args = frappe._dict(args)
- pi.posting_date = args.posting_date or today()
- if args.posting_time:
- pi.posting_time = args.posting_time
- if args.update_stock:
- pi.update_stock = 1
- if args.is_paid:
- pi.is_paid = 1
-
- if args.cash_bank_account:
- pi.cash_bank_account = args.cash_bank_account
-
- pi.company = args.company or "_Test Company"
- pi.supplier = args.supplier or "_Test Supplier"
- pi.currency = args.currency or "INR"
- pi.conversion_rate = args.conversion_rate or 1
- pi.is_return = args.is_return
- pi.return_against = args.return_against
- pi.is_subcontracted = args.is_subcontracted or 0
- pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
- pi.cost_center = args.parent_cost_center
-
- if args.total_tds or args.partial_tds:
- pi.apply_tds = 1
-
- pi.extend(
- "items",
- [
- {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 5,
- "received_qty": args.received_qty or 0,
- "rejected_qty": args.rejected_qty or 0,
- "rate": args.rate or 5000,
- "price_list_rate": args.price_list_rate or 5000,
- "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
- "discount_account": args.discount_account or None,
- "discount_amount": args.discount_amount or 0,
- "conversion_factor": 1.0,
- "serial_no": args.serial_no,
- "stock_uom": args.uom or "_Test UOM",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "project": args.project,
- "rejected_warehouse": args.rejected_warehouse or "",
- "rejected_serial_no": args.rejected_serial_no or "",
- "asset_location": args.location or "",
- "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
- "apply_tds": 1 if (args.total_tds or args.partial_tds) else 0
- },
- {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 5,
- "received_qty": args.received_qty or 0,
- "rejected_qty": args.rejected_qty or 0,
- "rate": args.rate or 5000,
- "price_list_rate": args.price_list_rate or 5000,
- "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
- "discount_account": args.discount_account or None,
- "discount_amount": args.discount_amount or 0,
- "conversion_factor": 1.0,
- "serial_no": args.serial_no,
- "stock_uom": args.uom or "_Test UOM",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "project": args.project,
- "rejected_warehouse": args.rejected_warehouse or "",
- "rejected_serial_no": args.rejected_serial_no or "",
- "asset_location": args.location or "",
- "allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0,
- "apply_tds": 1 if (args.total_tds) else 0
- },
- ]
- )
-
- pi.save()
- pi.submit()
- return pi
def make_purchase_invoice_against_cost_center(**args):
pi = frappe.new_doc("Purchase Invoice")
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 737338e..06e1c17 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -275,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 = []
@@ -291,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)
@@ -431,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(
@@ -559,4 +564,4 @@
) and certificate_limit > deducted_amount:
valid = True
- return valid
+ return valid
\ No newline at end of file
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..d29af92 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"
@@ -559,4 +599,4 @@
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
- ).insert()
+ ).insert()
\ No newline at end of file
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index a0bc6ba..16bc01d 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -67,13 +67,15 @@
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"):
@@ -1076,4 +1078,4 @@
def set_amounts_in_company_currency(self):
for d in self.doc.get(self.tax_field):
d.amount = flt(d.amount, d.precision("amount"))
- d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
+ d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
\ No newline at end of file
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..ffada07
--- /dev/null
+++ b/erpnext/patches/v14_0/update_tds_fields.py
@@ -0,0 +1,25 @@
+import frappe
+
+from erpnext.accounts.utils import 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"):
+ fiscal_year_details = get_fiscal_year(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()
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index dd957c7..c2e34a6 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1200,7 +1200,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);
@@ -1217,7 +1217,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",