Skip to content

Commit

Permalink
feat: TDS deduction using journal entry and other fixes (frappe#27451)
Browse files Browse the repository at this point in the history
* fix: TDS deduction using journal entry

* fix: Multi category application against single supplier

* refactor: TDS payable monthly report

* fix: Server side handling for default tax withholding category

* fix: Supplier filter for Journal Entry

* refactor: TDS computation summary report
  • Loading branch information
deepeshgarg007 authored Sep 27, 2021
1 parent b68ac24 commit cc5dd5c
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 290 deletions.
20 changes: 19 additions & 1 deletion erpnext/accounts/doctype/journal_entry/journal_entry.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
"voucher_type",
"naming_series",
"finance_book",
"tax_withholding_category",
"column_break1",
"from_template",
"company",
"posting_date",
"apply_tds",
"2_add_edit_gl_entries",
"accounts",
"section_break99",
Expand Down Expand Up @@ -498,16 +500,32 @@
"options": "Journal Entry Template",
"print_hide": 1,
"report_hide": 1
},
{
"depends_on": "eval:doc.apply_tds",
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"label": "Tax Withholding Category",
"mandatory_depends_on": "eval:doc.apply_tds",
"options": "Tax Withholding Category"
},
{
"default": "0",
"depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)",
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount "
}
],
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2020-10-30 13:56:01.121995",
"modified": "2021-09-09 15:31:14.484029",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
Expand Down
76 changes: 75 additions & 1 deletion erpnext/accounts/doctype/journal_entry/journal_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
get_party_account_based_on_invoice_discounting,
)
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import (
check_if_stock_and_account_balance_synced,
Expand Down Expand Up @@ -57,7 +60,8 @@ def validate(self):

self.validate_against_jv()
self.validate_reference_doc()
self.set_against_account()
if self.docstatus == 0:
self.set_against_account()
self.create_remarks()
self.set_print_format_fields()
self.validate_expense_claim()
Expand All @@ -66,6 +70,10 @@ def validate(self):
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
self.validate_stock_accounts()

if self.docstatus == 0:
self.apply_tax_withholding()

if not self.title:
self.title = self.get_title()

Expand Down Expand Up @@ -139,6 +147,72 @@ def validate_stock_accounts(self):
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
.format(account), StockAccountInvalidTransaction)

def apply_tax_withholding(self):
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map

if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'):
return

parties = [d.party for d in self.get('accounts') if d.party]
parties = list(set(parties))

if len(parties) > 1:
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))

account_type_map = get_account_type_map(self.company)
party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer'
doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice'
debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency'
rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency'

party_account = get_party_account(party_type.title(), parties[0], self.company)

net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account)
not in ('Tax', 'Chargeable'))

party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account)

inv = frappe._dict({
party_type: parties[0],
'doctype': doctype,
'company': self.company,
'posting_date': self.posting_date,
'net_total': net_total
})

tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)

if not tax_withholding_details:
return

accounts = []
for d in self.get('accounts'):
if d.get('account') == tax_withholding_details.get("account_head"):
d.update({
'account': tax_withholding_details.get("account_head"),
debit_or_credit: tax_withholding_details.get('tax_amount')
})

accounts.append(d.get('account'))

if d.get('account') == party_account:
d.update({
rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount')
})

if not accounts or tax_withholding_details.get("account_head") not in accounts:
self.append("accounts", {
'account': tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get('tax_amount'),
'against_account': parties[0]
})

to_remove = [d for d in self.get('accounts')
if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")]

for d in to_remove:
self.remove(d)

def update_inter_company_jv(self):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\
Expand Down
6 changes: 6 additions & 0 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,12 @@ def set_tax_withholding(self):
if not self.apply_tds:
return

if self.apply_tds and not self.get('tax_withholding_category'):
self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category')

if not self.tax_withholding_category:
return

tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)

if not tax_withholding_details:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company)
for account_detail in tax_withholding.accounts:
if company == account_detail.company:
return frappe._dict({
"tax_withholding_category": tax_withholding_category,
"account_head": account_detail.account,
"rate": tax_rate_detail.tax_withholding_rate,
"from_date": tax_rate_detail.from_date,
Expand Down Expand Up @@ -206,18 +207,39 @@ 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'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice'

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

return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice":
filters.update({
'apply_tds': 1,
'tax_withholding_category': tax_details.get('tax_withholding_category')
})

invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]

journal_entries = frappe.db.sql("""
SELECT j.name
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
WHERE
j.docstatus = 1
AND j.is_opening = 'No'
AND j.posting_date between %s and %s
AND ja.{dr_or_cr} > 0
AND ja.party in %s
""".format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1)

if journal_entries:
journal_entries = journal_entries[0]

return invoices + journal_entries

def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'):
# for advance vouchers, debit and credit is reversed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,29 @@ def test_tds_calculation_on_net_total(self):
for d in invoices:
d.cancel()

def test_multi_category_single_supplier(self):
frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category")
invoices = []

pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True)
pi.tax_withholding_category = "Test Service Category"
pi.save()
pi.submit()
invoices.append(pi)

# Second Invoice will apply TDS checked
pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True)
pi1.tax_withholding_category = "Test Goods Category"
pi1.save()
pi1.submit()
invoices.append(pi1)

self.assertEqual(pi1.taxes[0].tax_amount, 250)

#delete invoices to avoid clashing
for d in invoices:
d.cancel()

def cancel_invoices():
purchase_invoices = frappe.get_all("Purchase Invoice", {
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
Expand Down Expand Up @@ -251,7 +274,8 @@ def create_sales_invoice(**args):

def create_records():
# create a new suppliers
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']:
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3',
'Test TDS Supplier4', 'Test TDS Supplier5']:
if frappe.db.exists('Supplier', name):
continue

Expand Down Expand Up @@ -390,3 +414,39 @@ def create_tax_with_holding_category():
'account': 'TDS - _TC'
}]
}).insert()

if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Test Service Category",
"category_name": "Test Service Category",
"rates": [{
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 2000,
'cumulative_threshold': 2000
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()

if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "Test Goods Category",
"category_name": "Test Goods Category",
"rates": [{
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 2000,
'cumulative_threshold': 2000
}],
"accounts": [{
'company': '_Test Company',
'account': 'TDS - _TC'
}]
}).insert()
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
{
"add_total_row": 0,
"add_total_row": 1,
"columns": [],
"creation": "2018-08-21 11:25:00.551823",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2018-09-21 11:25:00.551823",
"modified": "2021-09-20 17:43:39.518851",
"modified_by": "Administrator",
"module": "Accounts",
"name": "TDS Computation Summary",
Expand Down
Loading

0 comments on commit cc5dd5c

Please sign in to comment.