Merge pull request #32646 from niralisatapara/tds_purchase_invoice

feat: Item Wise TDS Calculation
This commit is contained in:
Deepesh Garg 2022-11-04 13:08:10 +05:30 committed by GitHub
commit cbf82872fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 16 deletions

View File

@ -57,6 +57,8 @@
"column_break_28", "column_break_28",
"total", "total",
"net_total", "net_total",
"tax_withholding_net_total",
"base_tax_withholding_net_total",
"taxes_section", "taxes_section",
"taxes_and_charges", "taxes_and_charges",
"column_break_58", "column_break_58",
@ -89,7 +91,6 @@
"section_break_44", "section_break_44",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"additional_discount_account",
"column_break_46", "column_break_46",
"additional_discount_percentage", "additional_discount_percentage",
"discount_amount", "discount_amount",
@ -1421,6 +1422,26 @@
"label": "Is Old Subcontracting Flow", "label": "Is Old Subcontracting Flow",
"read_only": 1 "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", "collapsible_depends_on": "tax_withheld_vouchers",
"fieldname": "tax_withheld_vouchers_section", "fieldname": "tax_withheld_vouchers_section",
@ -1519,7 +1540,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-10-11 13:04:44.304389", "modified": "2022-11-04 01:02:44.544878",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -40,6 +40,7 @@
"discount_amount", "discount_amount",
"base_rate_with_margin", "base_rate_with_margin",
"sec_break2", "sec_break2",
"apply_tds",
"rate", "rate",
"amount", "amount",
"item_tax_template", "item_tax_template",
@ -868,6 +869,12 @@
"label": "Product Bundle", "label": "Product Bundle",
"options": "Product Bundle", "options": "Product Bundle",
"read_only": 1 "read_only": 1
},
{
"default": "1",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply TDS"
} }
], ],
"idx": 1, "idx": 1,

View File

@ -61,6 +61,9 @@ def get_party_details(inv):
def get_party_tax_withholding_details(inv, tax_withholding_category=None): 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 = "" pan_no = ""
parties = [] parties = []
party_type, party = get_party_details(inv) party_type, party = get_party_details(inv)
@ -242,7 +245,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if party_type == "Supplier": if party_type == "Supplier":
ldc = get_lower_deduction_certificate(tax_details, pan_no) ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted: if tax_deducted:
net_total = inv.net_total net_total = inv.tax_withholding_net_total
if ldc: if ldc:
tax_amount = get_tds_amount_from_ldc( tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total ldc, parties, pan_no, tax_details, posting_date, net_total
@ -272,6 +275,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" 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 = {} voucher_wise_amount = {}
vouchers = [] vouchers = []
@ -288,7 +296,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} {"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: for d in invoices_details:
vouchers.append(d.name) vouchers.append(d.name)
@ -392,7 +400,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0 tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} 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): if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None) invoice_filters.pop("apply_tds", None)
@ -415,12 +423,12 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
) )
supp_credit_amt += supp_jv_credit_amt 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) threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_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 cumulative_threshold and supp_credit_amt >= cumulative_threshold
): ):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint( if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
@ -428,11 +436,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
): ):
# Get net total again as TDS is calculated on net total # Get net total again as TDS is calculated on net total
# Grand is used to just check for threshold breach # Grand is used to just check for threshold breach
net_total = 0 net_total = (
if vouchers: frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)")
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
)
net_total += inv.net_total net_total += inv.tax_withholding_net_total
supp_credit_amt = net_total - cumulative_threshold supp_credit_amt = net_total - cumulative_threshold
if ldc and is_valid_certificate( if ldc and is_valid_certificate(
@ -440,7 +448,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
ldc.valid_upto, ldc.valid_upto,
inv.get("posting_date") or inv.get("transaction_date"), inv.get("posting_date") or inv.get("transaction_date"),
tax_deducted, tax_deducted,
inv.net_total, inv.tax_withholding_net_total,
ldc.certificate_limit, ldc.certificate_limit,
): ):
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
@ -523,7 +531,7 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
limit_consumed = frappe.db.get_value( limit_consumed = frappe.db.get_value(
"Purchase Invoice", "Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1}, {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
"sum(net_total)", "sum(tax_withholding_net_total)",
) )
if is_valid_certificate( if is_valid_certificate(

View File

@ -186,6 +186,46 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices): for d in reversed(invoices):
d.cancel() 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): def test_multi_category_single_supplier(self):
frappe.db.set_value( frappe.db.set_value(
"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category" "Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"

View File

@ -58,12 +58,25 @@ class calculate_taxes_and_totals(object):
self.initialize_taxes() self.initialize_taxes()
self.determine_exclusive_rate() self.determine_exclusive_rate()
self.calculate_net_total() self.calculate_net_total()
self.calculate_tax_withholding_net_total()
self.calculate_taxes() self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax() self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals() self.calculate_totals()
self._cleanup() self._cleanup()
self.calculate_total_net_weight() 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): def validate_item_tax_template(self):
for item in self.doc.get("items"): for item in self.doc.get("items"):
if item.item_code and item.get("item_tax_template"): if item.item_code and item.get("item_tax_template"):

View File

@ -317,3 +317,4 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v13_0.update_schedule_type_in_loans erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v14_0.update_tds_fields

View File

@ -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

View File

@ -1207,7 +1207,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"base_rounding_adjustment"], company_currency); "base_rounding_adjustment"], company_currency);
this.frm.set_currency_labels(["total", "net_total", "total_taxes_and_charges", "discount_amount", 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", "rounded_total", "in_words", "paid_amount", "write_off_amount", "operating_cost",
"scrap_material_cost", "rounding_adjustment", "raw_material_cost", "scrap_material_cost", "rounding_adjustment", "raw_material_cost",
"total_cost"], this.frm.doc.currency); "total_cost"], this.frm.doc.currency);
@ -1224,7 +1224,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
// toggle fields // 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_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_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", "base_paid_amount", "base_write_off_amount", "base_operating_cost", "base_raw_material_cost",