Skip to content

Commit

Permalink
Merge pull request #27258 from deepeshgarg007/tds_validity
Browse files Browse the repository at this point in the history
feat: Validity dates in Tax Withholding Rates
  • Loading branch information
deepeshgarg007 authored Sep 9, 2021
2 parents 3576668 + e7e2ce1 commit 9104796
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 259 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def get_data():
},
{
'label': _('References'),
'items': ['Period Closing Voucher', 'Tax Withholding Category']
'items': ['Period Closing Voucher']
},
{
'label': _('Target Details'),
Expand Down
17 changes: 11 additions & 6 deletions erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,10 +1151,11 @@ def test_purchase_invoice_advance_taxes(self):
tax_withholding_category = 'TDS - 194 - Dividends - Individual')

# Update tax withholding category with current fiscal year and rate details
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC')

# Create Purchase Order with TDS applied
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item')
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
posting_date='2021-09-15')
po.apply_tds = 1
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
po.save()
Expand Down Expand Up @@ -1226,16 +1227,20 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)

def update_tax_witholding_category(company, account, date):
def update_tax_witholding_category(company, account):
from erpnext.accounts.utils import get_fiscal_year

fiscal_year = get_fiscal_year(date=date, company=company)
fiscal_year = get_fiscal_year(fiscal_year='2021')

if not frappe.db.get_value('Tax Withholding Rate',
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
{'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]),
'to_date': ('<=', fiscal_year[2])}):
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
tds_category.set('rates', [])

tds_category.append('rates', {
'fiscal_year': fiscal_year[0],
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 2500,
'cumulative_threshold': 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,26 @@
from frappe.model.document import Document
from frappe.utils import cint, getdate

from erpnext.accounts.utils import get_fiscal_year


class TaxWithholdingCategory(Document):
pass
def validate(self):
self.validate_dates()
self.validate_thresholds()

def validate_dates(self):
last_date = None
for d in self.get('rates'):
if getdate(d.from_date) >= getdate(d.to_date):
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))

# validate overlapping of dates
if last_date and getdate(d.to_date) < getdate(last_date):
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))

def validate_thresholds(self):
for d in self.get('rates'):
if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold:
frappe.throw(_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(d.idx))

def get_party_details(inv):
party_type, party = '', ''
Expand Down Expand Up @@ -52,8 +67,8 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
if not parties:
parties.append(party)

fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
posting_date = inv.get('posting_date') or inv.get('transaction_date')
tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company)

if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
Expand All @@ -67,7 +82,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
tax_amount, tax_deducted = get_tax_amount(
party_type, parties,
inv, tax_details,
fiscal_year, pan_no
posting_date, pan_no
)

if party_type == 'Supplier':
Expand All @@ -77,16 +92,18 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):

return tax_row

def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)

tax_rate_detail = get_tax_withholding_rates(tax_withholding, fiscal_year)
tax_rate_detail = get_tax_withholding_rates(tax_withholding, posting_date)

for account_detail in tax_withholding.accounts:
if company == account_detail.company:
return frappe._dict({
"account_head": account_detail.account,
"rate": tax_rate_detail.tax_withholding_rate,
"from_date": tax_rate_detail.from_date,
"to_date": tax_rate_detail.to_date,
"threshold": tax_rate_detail.single_threshold,
"cumulative_threshold": tax_rate_detail.cumulative_threshold,
"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
Expand All @@ -95,13 +112,13 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
"round_off_tax_amount": tax_withholding.round_off_tax_amount
})

def get_tax_withholding_rates(tax_withholding, fiscal_year):
def get_tax_withholding_rates(tax_withholding, posting_date):
# returns the row that matches with the fiscal year from posting date
for rate in tax_withholding.rates:
if rate.fiscal_year == fiscal_year:
if getdate(rate.from_date) <= getdate(posting_date) <= getdate(rate.to_date):
return rate

frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
frappe.throw(_("No Tax Withholding data found for the current posting date."))

def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
row = {
Expand Down Expand Up @@ -143,38 +160,38 @@ def get_tax_row_for_tds(tax_details, tax_amount):
"account_head": tax_details.account_head
}

def get_lower_deduction_certificate(fiscal_year, pan_no):
ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name')
def get_lower_deduction_certificate(tax_details, pan_no):
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
{
'pan_no': pan_no,
'valid_from': ('>=', tax_details.from_date),
'valid_upto': ('<=', tax_details.to_date)
}, 'name')

if ldc_name:
return frappe.get_doc('Lower Deduction Certificate', ldc_name)

def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
fiscal_year = fiscal_year_details[0]


vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type)
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
to_date=tax_details.to_date, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers

tax_deducted = 0
if taxable_vouchers:
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)

tax_amount = 0
posting_date = inv.get('posting_date') or inv.get('transaction_date')
if party_type == 'Supplier':
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
ldc = get_lower_deduction_certificate(tax_details, pan_no)
if tax_deducted:
net_total = inv.net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total)
tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total)
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
else:
tax_amount = get_tds_amount(
ldc, parties, inv, tax_details,
fiscal_year_details, tax_deducted, vouchers
)
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)

elif party_type == 'Customer':
if tax_deducted:
Expand All @@ -183,29 +200,26 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
else:
# if no TCS has been charged in FY,
# then chargeable value is "prev invoices + advances" value which cross the threshold
tax_amount = get_tcs_amount(
parties, inv, tax_details,
fiscal_year_details, vouchers, advance_vouchers
)
tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers)

return tax_amount, tax_deducted

def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'

filters = {
dr_or_cr: ['>', 0],
'company': company,
'party_type': party_type,
'party': ['in', parties],
'fiscal_year': fiscal_year,
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
'is_opening': 'No',
'is_cancelled': 0
}

return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]

def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'):
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
# for advance vouchers, debit and credit is reversed
dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'

Expand All @@ -218,29 +232,28 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None
'against_voucher': ['is', 'not set']
}

if fiscal_year:
filters['fiscal_year'] = fiscal_year
if company:
filters['company'] = company
if from_date and to_date:
filters['posting_date'] = ['between', (from_date, to_date)]

return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]

def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
def get_deducted_tax(taxable_vouchers, tax_details):
# check if TDS / TCS account is already charged on taxable vouchers
filters = {
'is_cancelled': 0,
'credit': ['>', 0],
'fiscal_year': fiscal_year,
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
'account': tax_details.account_head,
'voucher_no': ['in', taxable_vouchers],
}
field = "sum(credit)"
field = "credit"

return frappe.db.get_value('GL Entry', filters, field) or 0.0
entries = frappe.db.get_all('GL Entry', filters, pluck=field)
return sum(entries)

def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0
invoice_filters = {
'name': ('in', vouchers),
Expand All @@ -264,7 +277,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.net_total

debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company)
debit_note_amount = get_debit_note_amount(parties, tax_details.from_date, tax_details.to_date, inv.company)
supp_credit_amt -= debit_note_amount

threshold = tax_details.get('threshold', 0)
Expand Down Expand Up @@ -292,9 +305,8 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu

return tds_amount

def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
tcs_amount = 0
fiscal_year, _, _ = fiscal_year_details

# sum of debit entries made from sales invoices
invoiced_amt = frappe.db.get_value('GL Entry', {
Expand All @@ -313,14 +325,14 @@ def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv
}, 'sum(credit)') or 0.0

# sum of credit entries made from sales invoice
credit_note_amt = frappe.db.get_value('GL Entry', {
credit_note_amt = sum(frappe.db.get_all('GL Entry', {
'is_cancelled': 0,
'credit': ['>', 0],
'party': ['in', parties],
'fiscal_year': fiscal_year,
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
'company': inv.company,
'voucher_type': 'Sales Invoice',
}, 'sum(credit)') or 0.0
}, pluck='credit'))

cumulative_threshold = tax_details.get('cumulative_threshold', 0)

Expand All @@ -339,7 +351,7 @@ def get_invoice_total_without_tcs(inv, tax_details):

return inv.grand_total - tcs_tax_row_amount

def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total):
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value('Purchase Invoice', {
'supplier': ('in', parties),
Expand All @@ -356,14 +368,13 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post

return tds_amount

def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
_, year_start_date, year_end_date = fiscal_year_details
def get_debit_note_amount(suppliers, from_date, to_date, company=None):

filters = {
'supplier': ['in', suppliers],
'is_return': 1,
'docstatus': 1,
'posting_date': ['between', (year_start_date, year_end_date)]
'posting_date': ['between', (from_date, to_date)]
}
fields = ['abs(sum(net_total)) as net_total']

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,16 +313,16 @@ def create_records():
}).insert()

def create_tax_with_holding_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]

# Cummulative thresold
fiscal_year = get_fiscal_year(today(), company="_Test Company")
# Cumulative threshold
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Cumulative Threshold TDS",
"category_name": "10% TDS",
"rates": [{
'fiscal_year': fiscal_year,
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000.00
Expand All @@ -339,7 +339,8 @@ def create_tax_with_holding_category():
"name": "Cumulative Threshold TCS",
"category_name": "10% TCS",
"rates": [{
'fiscal_year': fiscal_year,
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000.00
Expand All @@ -357,7 +358,8 @@ def create_tax_with_holding_category():
"name": "Single Threshold TDS",
"category_name": "10% TDS",
"rates": [{
'fiscal_year': fiscal_year,
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 20000.00,
'cumulative_threshold': 0
Expand All @@ -377,7 +379,8 @@ def create_tax_with_holding_category():
"consider_party_ledger_amount": 1,
"tax_on_excess_amount": 1,
"rates": [{
'fiscal_year': fiscal_year,
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000
Expand Down
Loading

0 comments on commit 9104796

Please sign in to comment.