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 0cb872c4b8..c871af9428 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 frappe.utils import flt, getdate, cint 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 @@ 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}') @@ -64,7 +81,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': @@ -74,16 +91,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, @@ -92,13 +111,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 = { @@ -140,38 +159,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: @@ -180,14 +199,11 @@ 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 = { @@ -195,14 +211,14 @@ def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): '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 @@ 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: @@ -224,20 +238,21 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None 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 @@ 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) @@ -289,9 +304,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', { @@ -310,14 +324,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) @@ -336,7 +350,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), @@ -353,14 +367,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'] 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 0f921db678..138aaec6ab 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 @@ 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 @@ -338,7 +338,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 @@ -356,7 +357,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 @@ -376,7 +378,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 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 1e8194af6e..d2c505c630 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 05c385b6ba..ae83d5eb96 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -300,4 +300,5 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries 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 0000000000..2563d8a8c4 --- /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