diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json index bc7f965956..2ec0b7f70c 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json @@ -147,8 +147,9 @@ } }, "Duties and Taxes": { - "account_type": "Tax", - "is_group": 1 + "TDS": { + "account_type": "Tax" + } }, "Loans (Liabilities)": { "Secured Loans": {}, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f983868a0f..0a22ae0a47 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -7,7 +7,7 @@ import frappe, erpnext, json from frappe import _, scrub, ValidationError from frappe.utils import flt, comma_or, nowdate from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on -from erpnext.accounts.party import get_party_account +from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.general_ledger import make_gl_entries @@ -43,6 +43,7 @@ class PaymentEntry(AccountsController): def validate(self): self.setup_party_account_field() + self.set_tax_withholding() self.set_missing_values() self.validate_payment_type() self.validate_party_details() @@ -510,6 +511,27 @@ class PaymentEntry(AccountsController): def on_recurring(self, reference_doc, subscription_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() + + def set_tax_withholding(self): + if self.party_type != 'Supplier': + return + + self.supplier = self.party + tax_withholding_details = get_patry_tax_withholding_details(self) + + for tax_details in tax_withholding_details: + if self.deductions: + if tax_details['tax']['account_head'] not in [deduction.account for deduction in self.deductions]: + self.append('deductions', self.calculate_deductions(tax_details)) + else: + self.append('deductions', self.calculate_deductions(tax_details)) + + def calculate_deductions(self, tax_details): + return { + "account": tax_details['tax']['account_head'], + "cost_center": frappe.db.get_value("Company", self.company, "cost_center"), + "amount": self.total_allocated_amount * (tax_details['tax']['rate'] / 100) + } @frappe.whitelist() def get_outstanding_reference_documents(args): diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3e375e5051..c9cf47d114 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -8,7 +8,7 @@ from frappe import _, throw import frappe.defaults from erpnext.controllers.buying_controller import BuyingController -from erpnext.accounts.party import get_party_account, get_due_date +from erpnext.accounts.party import get_party_account, get_due_date, get_patry_tax_withholding_details from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po from erpnext.stock import get_warehouse_account_map @@ -46,6 +46,7 @@ class PurchaseInvoice(BuyingController): self.is_opening = 'No' self.validate_posting_time() + self.set_tax_withholding() super(PurchaseInvoice, self).validate() if not self.is_return: @@ -53,7 +54,6 @@ class PurchaseInvoice(BuyingController): self.pr_required() self.validate_supplier_invoice() - # validate cash purchase if (self.is_paid == 1): self.validate_cash() @@ -168,7 +168,6 @@ class PurchaseInvoice(BuyingController): super(PurchaseInvoice, self).validate_warehouse() - def validate_item_code(self): for d in self.get('items'): if not d.item_code: @@ -731,6 +730,20 @@ class PurchaseInvoice(BuyingController): def on_recurring(self, reference_doc, subscription_doc): self.due_date = None + def set_tax_withholding(self): + """ + 1. Get TDS Configurations against Supplier + """ + + tax_withholding_details = get_patry_tax_withholding_details(self) + for tax_details in tax_withholding_details: + if flt(self.get("rounded_total") or self.grand_total) >= flt(tax_details['threshold']): + if self.taxes: + if tax_details['tax']['description'] not in [tax.description for tax in self.taxes]: + self.append('taxes', tax_details['tax']) + else: + self.append('taxes', tax_details['tax']) + @frappe.whitelist() def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index f02a52043e..a590776e68 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -13,6 +13,68 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_default", + "fieldtype": "Check", + "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, + "label": "Is Default", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enabled", + "fieldtype": "Check", + "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, + "label": "Enabled", + "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 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -271,7 +333,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 19:17:12.494050", + "modified": "2018-05-11 14:25:07.474461", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", 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 4940c4f3fe..61f4b60c8b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -7,4 +7,7 @@ import frappe from frappe.model.document import Document class TaxWithholdingCategory(Document): - pass + def validate(self): + if not frappe.db.get_value("Tax Withholding Category", + {"is_default": 1, "name": ("!=", self.name)}, "name"): + self.is_default = 1 \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 652272dbe1..75089b2ff4 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -448,7 +448,6 @@ def get_dashboard_info(party_type, party): return info - def get_party_shipping_address(doctype, name): """ Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true. @@ -476,3 +475,60 @@ def get_party_shipping_address(doctype, name): return out[0][0] else: return '' + +def get_patry_tax_withholding_details(ref_doc): + supplier = frappe.get_doc("Supplier", ref_doc.supplier) + tax_withholding_details = [] + print(supplier) + for tax in supplier.tax_withholding_config: + tax_mapper = get_tax_mapper() + + set_tax_withholding_details(tax_mapper, ref_doc, tax_withholding_category=tax.tax_withholding_category) + + if tax.valid_till and date_diff(tax.valid_till, ref_doc.posting_date) > 0: + tax_mapper.update({ + "rate": tax.applicable_percentage + }) + + prepare_tax_withholding_details(tax_mapper, tax_withholding_details) + + return tax_withholding_details + +def prepare_tax_withholding_details(tax_mapper, tax_withholding_details): + if tax_mapper.get('account_head'): + + tax_withholding_details.append({ + "threshold": tax_mapper['threshold'], + "tax": tax_mapper + }) + + del tax_mapper['threshold'] + +def set_tax_withholding_details(tax_mapper, ref_doc, tax_withholding_category=None, use_default=0): + if tax_withholding_category: + tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) + else: + tax_withholding = frappe.get_doc("Tax Withholding Category", {'is_default': 1, 'enabled': 1}) + + if tax_withholding.book_on_invoice and ref_doc.doctype=='Purchase Invoice' \ + or tax_withholding.book_on_advance and ref_doc.doctype in ('Payment Entry', 'Journal Entry'): + + for account_detail in tax_withholding.accounts: + if ref_doc.company == account_detail.company: + tax_mapper.update({ + "account_head": account_detail.account, + "rate": tax_withholding.percent_of_tax_withheld, + "threshold": tax_withholding.threshold, + "description": tax_withholding.name + }) + +def get_tax_mapper(): + return { + "category": "Total", + "add_deduct_tax": "Deduct", + "charge_type": "On Net Total", + "rate": 0, + "description": '', + "account_head": '', + "threshold": 0.0 + } diff --git a/erpnext/buying/doctype/party_tax_withholding_config/__init__.py b/erpnext/buying/doctype/party_tax_withholding_config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json new file mode 100644 index 0000000000..320485b318 --- /dev/null +++ b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json @@ -0,0 +1,166 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-11 13:32:33.825307", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tax_withholding_category", + "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": "Tax Withholding Category", + "length": 0, + "no_copy": 0, + "options": "Tax Withholding Category", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "valid_till", + "fieldtype": "Date", + "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": "Valid Till", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "applicable_percent", + "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": "Applicable Percent", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "certificate_received", + "fieldtype": "Check", + "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, + "label": "Certificate Received", + "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 + } + ], + "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-05-11 13:35:44.424855", + "modified_by": "Administrator", + "module": "Buying", + "name": "Party Tax Withholding Config", + "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 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py new file mode 100644 index 0000000000..bec7e83f23 --- /dev/null +++ b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PartyTaxWithholdingConfig(Document): + pass diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index d342e115b7..eedbac1dff 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -233,7 +233,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -960,6 +960,69 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "default_tax_withholding_config", + "fieldtype": "Section 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, + "label": "Default Tax Withholding Config", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tax_withholding_config", + "fieldtype": "Table", + "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, + "label": "Tax Withholding Account", + "length": 0, + "no_copy": 0, + "options": "Party Tax Withholding Config", + "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 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1117,38 +1180,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_withholding_category", - "fieldtype": "Link", - "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, - "label": "Tax Withholding Category", - "length": 0, - "no_copy": 0, - "options": "Tax Withholding Category", - "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 } ], "has_web_view": 0, @@ -1163,7 +1194,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-11 12:19:52.519026", + "modified": "2018-05-11 15:15:19.912308", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 3527197edc..cc3fb7ff20 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -2766,7 +2766,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-10 07:52:24.326361", + "modified": "2018-05-11 12:48:46.435484", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 0073d3311c..70960d714b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -16,7 +16,7 @@ def setup(company=None, patch=True): add_print_formats() if not patch: update_address_template() - make_fixtures() + make_fixtures(company) def update_address_template(): with open(os.path.join(os.path.dirname(__file__), 'address_template.html'), 'r') as f: @@ -189,15 +189,13 @@ def make_custom_fields(): create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch) -def make_fixtures(): - docs = [ - {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'}, - {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'}, - {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'} - ] +def make_fixtures(company=None): + docs = [] + company = company.name if company else frappe.db.get_value("Global Defaults", None, "default_company") + + set_salary_components(docs) + set_tds_account(docs, company) + set_tax_withholding_category(docs, company) for d in docs: try: @@ -206,3 +204,43 @@ def make_fixtures(): doc.insert() except frappe.NameError: pass + +def set_salary_components(docs): + docs.extend([ + {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'}, + {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'}, + {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'} + ]) + +def set_tax_withholding_category(docs, company): + accounts = [] + tds_account = frappe.db.get_value("Account", filter={"account_type": "Payable", + "account_name": "TDS", "company": company}) + + if company and tds_account: + accounts = [ + { + 'company': company, + 'account': tds_account + } + ] + + docs.extend([ + { + 'doctype': 'Tax Withholding Category', '__newname': 'TDS', + 'percent_of_tax_withheld': 10,'threshold': 150000, 'book_on_invoice': 1, + 'book_on_advance': 0, "withhold_cumulative_tax_amount": 0, + 'accounts': accounts + } + ]) + +def set_tds_account(docs, company): + docs.extend([ + { + 'doctype': 'Account', 'account_name': 'TDS', 'account_type': 'Tax', + 'parent_account': 'Duties and Taxes', 'company': company + } + ]) \ No newline at end of file