feat: Validity dates in Tax Withholding Rates
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 0cb872c..c871af9 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -10,7 +10,24 @@
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(r.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 = '', ''
@@ -49,8 +66,8 @@
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}')
@@ -64,7 +81,7 @@
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':
@@ -74,16 +91,18 @@
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,
@@ -92,13 +111,13 @@
"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 = {
@@ -140,38 +159,38 @@
"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:
@@ -180,14 +199,11 @@
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 = {
@@ -195,14 +211,14 @@
'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'
@@ -215,8 +231,6 @@
'against_voucher': ['is', 'not set']
}
- if fiscal_year:
- filters['fiscal_year'] = fiscal_year
if company:
filters['company'] = company
if from_date and to_date:
@@ -224,20 +238,21 @@
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),
@@ -261,7 +276,7 @@
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)
@@ -289,9 +304,8 @@
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', {
@@ -310,14 +324,14 @@
}, '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)
@@ -336,7 +350,7 @@
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),
@@ -353,14 +367,13 @@
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']
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 0f921db..138aaec 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
@@ -312,16 +312,16 @@
}).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
@@ -338,7 +338,8 @@
"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
@@ -356,7 +357,8 @@
"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
@@ -376,7 +378,8 @@
"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
diff --git a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
index 1e8194a..d2c505c 100644
--- a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
+++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
@@ -1,202 +1,72 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-07-17 16:53:13.716665",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-07-17 16:53:13.716665",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "from_date",
+ "to_date",
+ "tax_withholding_rate",
+ "column_break_3",
+ "single_threshold",
+ "cumulative_threshold"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Fiscal Year",
- "length": 0,
- "no_copy": 0,
- "options": "Fiscal Year",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 1,
+ "fieldname": "tax_withholding_rate",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Tax Withholding Rate",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 2,
- "fieldname": "tax_withholding_rate",
- "fieldtype": "Float",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Tax Withholding Rate",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 2,
+ "fieldname": "single_threshold",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Single Transaction Threshold"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 3,
- "fieldname": "single_threshold",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Single Transaction Threshold",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
- },
+ "columns": 3,
+ "fieldname": "cumulative_threshold",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Cumulative Transaction Threshold"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 3,
- "fieldname": "cumulative_threshold",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Cumulative Transaction Threshold",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "columns": 2,
+ "fieldname": "from_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "From Date",
+ "reqd": 1
+ },
+ {
+ "columns": 2,
+ "fieldname": "to_date",
+ "fieldtype": "Date",
+ "in_list_view": 1,
+ "label": "To Date",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-07-17 17:13:09.819580",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Tax Withholding Rate",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-31 11:42:12.213977",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Tax Withholding Rate",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 05c385b..ae83d5e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -300,4 +300,5 @@
erpnext.patches.v13_0.einvoicing_deprecation_warning
erpnext.patches.v14_0.delete_einvoicing_doctypes
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
-erpnext.patches.v13_0.validate_options_for_data_field
\ No newline at end of file
+erpnext.patches.v13_0.validate_options_for_data_field
+erpnext.patches.v13_0.update_dates_in_tax_withholding_category
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py
new file mode 100644
index 0000000..2563d8a
--- /dev/null
+++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2021, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from erpnext.accounts.utils import get_fiscal_year
+
+def execute():
+ frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate')
+ tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year'])
+
+ fiscal_year_map = {}
+ for rate in tds_category_rates:
+ if not fiscal_year_map.get(rate.fiscal_year):
+ fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year)
+
+ from_date = fiscal_year_map.get(rate.fiscal_year)[1]
+ to_date = fiscal_year_map.get(rate.fiscal_year)[2]
+
+ frappe.db.set_value('Tax Withholding Rate', rate.name, {
+ 'from_date': from_date,
+ 'to_date': to_date
+ })
\ No newline at end of file