feat: TDS deduction using journal entry and other fixes (#27451)

* 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

(cherry picked from commit cc5dd5c67d)

# Conflicts:
#	erpnext/accounts/doctype/journal_entry/journal_entry.json
#	erpnext/accounts/doctype/journal_entry/journal_entry.py
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
#	erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
#	erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
#	erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
This commit is contained in:
Deepesh Garg
2021-09-27 21:43:20 +05:30
committed by Mergify
parent f5160dc83d
commit 8f36bdc045
7 changed files with 417 additions and 0 deletions

View File

@@ -13,8 +13,11 @@
"voucher_type",
"naming_series",
"finance_book",
<<<<<<< HEAD
"process_deferred_accounting",
"reversal_of",
=======
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
"tax_withholding_category",
"column_break1",
"from_template",
@@ -518,6 +521,7 @@
"fieldname": "apply_tds",
"fieldtype": "Check",
"label": "Apply Tax Withholding Amount "
<<<<<<< HEAD
},
{
"depends_on": "eval:doc.docstatus",
@@ -533,13 +537,19 @@
"label": "Process Deferred Accounting",
"options": "Process Deferred Accounting",
"read_only": 1
=======
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
}
],
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
"links": [],
<<<<<<< HEAD
"modified": "2022-06-23 22:01:32.348337",
=======
"modified": "2021-09-09 15:31:14.484029",
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -75,7 +75,11 @@ class JournalEntry(AccountsController):
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
self.validate_inter_company_accounts()
<<<<<<< HEAD
self.validate_depr_entry_voucher_type()
=======
self.validate_stock_accounts()
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
if self.docstatus == 0:
self.apply_tax_withholding()
@@ -272,6 +276,72 @@ class JournalEntry(AccountsController):
asset.set_status()
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"

View File

@@ -1468,20 +1468,29 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds:
return
<<<<<<< HEAD
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 self.apply_tds and not self.get('tax_withholding_category'):
self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category')
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
if not self.tax_withholding_category:
return
<<<<<<< HEAD
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
self, self.tax_withholding_category
)
# Adjust TDS paid on advances
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
=======
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
if not tax_withholding_details:
return

View File

@@ -122,6 +122,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company)
for account_detail in tax_withholding.accounts:
if company == account_detail.company:
<<<<<<< HEAD
return frappe._dict(
{
"tax_withholding_category": tax_withholding_category,
@@ -140,6 +141,21 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, 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,
"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,
"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
"round_off_tax_amount": tax_withholding.round_off_tax_amount
})
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
def get_tax_withholding_rates(tax_withholding, posting_date):
# returns the row that matches with the fiscal year from posting date
@@ -258,6 +274,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if cint(tax_details.round_off_tax_amount):
tax_amount = normal_round(tax_amount)
<<<<<<< HEAD
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
@@ -278,6 +295,43 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
filters.update(
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
=======
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 = {
'company': company,
frappe.scrub(party_type): ['in', parties],
'posting_date': ['between', (tax_details.from_date, tax_details.to_date)],
'is_opening': 'No',
'docstatus': 1
}
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
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])

View File

@@ -186,8 +186,39 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
<<<<<<< HEAD
def test_tds_deduction_for_po_via_payment_entry(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
=======
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']],
'docstatus': 1
}, pluck="name")
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
frappe.db.set_value(
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
@@ -410,6 +441,7 @@ def create_sales_invoice(**args):
def create_records():
# create a new suppliers
<<<<<<< HEAD
for name in [
"Test TDS Supplier",
"Test TDS Supplier1",
@@ -422,6 +454,11 @@ def create_records():
"Test TDS Supplier8",
]:
if frappe.db.exists("Supplier", name):
=======
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):
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
continue
frappe.get_doc(
@@ -552,6 +589,7 @@ def create_tax_with_holding_category():
).insert()
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
<<<<<<< HEAD
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
@@ -629,3 +667,60 @@ def create_tax_with_holding_category():
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
=======
frappe.get_doc({
"doctype": "Tax Withholding Category",
"name": "New TDS Category",
"category_name": "New TDS Category",
"round_off_tax_amount": 1,
"consider_party_ledger_amount": 1,
"tax_on_excess_amount": 1,
"rates": [{
'from_date': fiscal_year[1],
'to_date': fiscal_year[2],
'tax_withholding_rate': 10,
'single_threshold': 0,
'cumulative_threshold': 30000
}],
"accounts": [{
'company': '_Test Company',
'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()
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))

View File

@@ -14,6 +14,7 @@ def execute(filters=None):
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters)
<<<<<<< HEAD
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
@@ -21,6 +22,14 @@ def execute(filters=None):
return columns, final_result
=======
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
final_result = group_by_supplier_and_category(res)
return columns, final_result
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
def validate_filters(filters):
"""Validate if dates are properly set and lie in the same fiscal year"""
@@ -34,6 +43,7 @@ def validate_filters(filters):
filters["fiscal_year"] = from_year
<<<<<<< HEAD
def group_by_supplier_and_category(data):
supplier_category_wise_map = {}
@@ -60,6 +70,28 @@ def group_by_supplier_and_category(data):
supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
"tds_deducted"
] += row.get("tds_deducted", 0.0)
=======
def group_by_supplier_and_category(data):
supplier_category_wise_map = {}
for row in data:
supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), {
'pan': row.get('pan'),
'supplier': row.get('supplier'),
'supplier_name': row.get('supplier_name'),
'section_code': row.get('section_code'),
'entity_type': row.get('entity_type'),
'tds_rate': row.get('tds_rate'),
'total_amount_credited': 0.0,
'tds_deducted': 0.0
})
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \
row.get('total_amount_credited', 0.0)
supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \
row.get('tds_deducted', 0.0)
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
final_result = get_final_result(supplier_category_wise_map)
@@ -73,7 +105,10 @@ def get_final_result(supplier_category_wise_map):
return out
<<<<<<< HEAD
=======
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
def get_columns(filters):
columns = [
{"label": _("PAN"), "fieldname": "pan", "fieldtype": "Data", "width": 90},
@@ -91,6 +126,7 @@ def get_columns(filters):
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
)
<<<<<<< HEAD
columns.extend(
[
{
@@ -116,5 +152,40 @@ def get_columns(filters):
},
]
)
=======
columns.extend([
{
"label": _("Section Code"),
"options": "Tax Withholding Category",
"fieldname": "section_code",
"fieldtype": "Link",
"width": 180
},
{
"label": _("Entity Type"),
"fieldname": "entity_type",
"fieldtype": "Data",
"width": 180
},
{
"label": _("TDS Rate %"),
"fieldname": "tds_rate",
"fieldtype": "Percent",
"width": 90
},
{
"label": _("Total Amount Credited"),
"fieldname": "total_amount_credited",
"fieldtype": "Float",
"width": 90
},
{
"label": _("Amount of TDS Deducted"),
"fieldname": "tds_deducted",
"fieldtype": "Float",
"width": 90
}
])
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
return columns

View File

@@ -8,11 +8,19 @@ from frappe import _
def execute(filters=None):
validate_filters(filters)
<<<<<<< HEAD
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
columns = get_columns(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
=======
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
columns = get_columns(filters)
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
return columns, res
@@ -21,11 +29,18 @@ def validate_filters(filters):
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
<<<<<<< HEAD
def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map):
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(tds_docs)
=======
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
gle_map = get_gle_map(filters, tds_docs)
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
out = []
for name, details in gle_map.items():
@@ -38,6 +53,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
posting_date = entry.posting_date
voucher_type = entry.voucher_type
<<<<<<< HEAD
if voucher_type == "Journal Entry":
suppliers = journal_entry_party_map.get(name)
if suppliers:
@@ -75,11 +91,38 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
"ref_no": name,
}
)
=======
if entry.account in tds_accounts:
tds_deducted += (entry.credit - entry.debit)
total_amount_credited += (entry.credit - entry.debit)
if rate and tds_deducted:
row = {
'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan,
'supplier': supplier_map.get(supplier).name
}
if filters.naming_series == 'Naming Series':
row.update({'supplier_name': supplier_map.get(supplier).supplier_name})
row.update({
'section_code': tax_withholding_category,
'entity_type': supplier_map.get(supplier).supplier_type,
'tds_rate': rate,
'total_amount_credited': total_amount_credited,
'tds_deducted': tds_deducted,
'transaction_date': posting_date,
'transaction_type': voucher_type,
'ref_no': name
})
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
out.append(row)
return out
<<<<<<< HEAD
def get_supplier_pan_map():
supplier_map = frappe._dict()
@@ -94,13 +137,33 @@ def get_supplier_pan_map():
def get_gle_map(documents):
=======
def get_supplier_pan_map():
supplier_map = frappe._dict()
suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name'])
for d in suppliers:
supplier_map[d.name] = d
return supplier_map
def get_gle_map(filters, documents):
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
# create gle_map of the form
# {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {}
<<<<<<< HEAD
gle = frappe.db.get_all(
"GL Entry",
{"voucher_no": ["in", documents], "is_cancelled": 0},
=======
gle = frappe.db.get_all('GL Entry',
{
"voucher_no": ["in", documents],
"credit": (">", 0)
},
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
)
@@ -173,13 +236,17 @@ def get_columns(filters):
return columns
<<<<<<< HEAD
=======
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
def get_tds_docs(filters):
tds_documents = []
purchase_invoices = []
payment_entries = []
journal_entries = []
tax_category_map = {}
<<<<<<< HEAD
or_filters = {}
journal_entry_party_map = {}
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
@@ -206,6 +273,23 @@ def get_tds_docs(filters):
or_filters=or_filters,
fields=["voucher_no", "voucher_type", "against", "party"],
)
=======
tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')},
pluck="account")
query_filters = {
"credit": ('>', 0),
"account": ("in", tds_accounts),
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
"is_cancelled": 0
}
if filters.get('supplier'):
query_filters.update({'against': filters.get('supplier')})
tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"])
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
@@ -218,6 +302,7 @@ def get_tds_docs(filters):
tds_documents.append(d.voucher_no)
if purchase_invoices:
<<<<<<< HEAD
get_tax_category_map(purchase_invoices, "Purchase Invoice", tax_category_map)
if payment_entries:
@@ -269,3 +354,26 @@ def get_tax_rate_map(filters):
)
return frappe._dict(rate_map)
=======
get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map)
if payment_entries:
get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map)
if journal_entries:
get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map)
return tds_documents, tds_accounts, tax_category_map
def get_tax_category_map(vouchers, doctype, tax_category_map):
tax_category_map.update(frappe._dict(frappe.get_all(doctype,
filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1)))
def get_tax_rate_map(filters):
rate_map = frappe.get_all('Tax Withholding Rate', filters={
'from_date': ('<=', filters.get('from_date')),
'to_date': ('>=', filters.get('to_date'))
}, fields=['parent', 'tax_withholding_rate'], as_list=1)
return frappe._dict(rate_map)
>>>>>>> cc5dd5c67d (feat: TDS deduction using journal entry and other fixes (#27451))