[Enhancement] Tax Withholding Category (#15064)
* add single, cumulative threshold, remove checkboxes
* remove tds child table from supplier & add tds link field
* add description field in tax withholding category
* add tax withholding data for indian setup, some fixes
* add a checkbox for tax withholding in purchase invoice
* remove supplier's child table for tds
* enable tds field if supplier has tds set inits master
* move rates data to child table - adding fiscal year support
* change bootstrap data according to child table config of tds
* show category name in list view
* loyalty program fixes
* moved tax calculation to tax_withholding.py
- calculation for tds amount for cumulative threshold from gl entry
* add fiscal year dependency in company test
* minor loyalty program fix
* minor tier calculation fix
* minor handling duplicate exception
* toggle apply_tds according to supplier, code rectify
* minor fixes for loyalty program
* test case for single and cumulative threshold
* codacy fix
diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
index 96eda7f..d840304 100644
--- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py
@@ -13,28 +13,47 @@
pass
-def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None):
+def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=None, include_expired_entry=False):
if not expiry_date:
expiry_date = today()
- args_list = [customer, loyalty_program, expiry_date, expiry_date]
condition = ''
if company:
- condition = " and company=%s "
- args_list.append(company)
+ condition = " and company='%s' " % frappe.db.escape(company)
+ if not include_expired_entry:
+ condition += " and expiry_date>='%s' " % expiry_date
+
loyalty_point_details = frappe.db.sql('''select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
- where customer=%s and loyalty_program=%s
- and expiry_date>=%s and posting_date <= %s
+ where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
- group by customer'''.format(condition=condition), tuple(args_list), as_dict=1)
+ group by customer'''.format(condition=condition),
+ (customer, loyalty_program, expiry_date), as_dict=1)
+
if loyalty_point_details:
return loyalty_point_details[0]
else:
return {"loyalty_points": 0, "total_spent": 0}
@frappe.whitelist()
-def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False):
+def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
+ lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
+ loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
+ lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
+
+ tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
+ key=lambda rule:rule.min_spent, reverse=True)
+ for i, d in enumerate(tier_spent_level):
+ if i==0 or (lp_details.total_spent+current_transaction_amount) <= d.min_spent:
+ lp_details.tier_name = d.tier_name
+ lp_details.collection_factor = d.collection_factor
+ else:
+ break
+
+ return lp_details
+
+@frappe.whitelist()
+def get_loyalty_program_details(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False):
lp_details = frappe._dict()
if not loyalty_program:
@@ -51,17 +70,6 @@
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update({"loyalty_program": loyalty_program.name})
lp_details.update(loyalty_program.as_dict())
-
- lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company))
-
- tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
- key=lambda rule:rule.min_spent, reverse=True)
- for i, d in enumerate(tier_spent_level):
- if i == 0 or lp_details.total_spent < d.min_spent:
- lp_details.tier_name = d.tier_name
- lp_details.collection_factor = d.collection_factor
- else:
- break
return lp_details
@frappe.whitelist()
@@ -95,7 +103,7 @@
frappe.throw(_("The Loyalty Program isn't valid for the selected company"))
if loyalty_program and points_to_redeem:
- loyalty_program_details = get_loyalty_program_details(ref_doc.customer, loyalty_program,
+ loyalty_program_details = get_loyalty_program_details_with_points(ref_doc.customer, loyalty_program,
posting_date, ref_doc.company)
if points_to_redeem > loyalty_program_details.loyalty_points:
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
index 87a0cb8..99553b4 100644
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
+++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
@@ -6,7 +6,7 @@
import frappe
import unittest
from frappe.utils import today, cint, flt, getdate
-from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details
+from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
class TestLoyaltyProgram(unittest.TestCase):
@classmethod
@@ -15,6 +15,7 @@
create_records()
def test_loyalty_points_earned_single_tier(self):
+ frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si_original = create_sales_invoice_record()
si_original.insert()
@@ -50,6 +51,7 @@
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self):
+ frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
# assign multiple tier program to the customer
customer = frappe.get_doc('Customer', {"customer_name": "Test Loyalty Customer"})
customer.loyalty_program = frappe.get_doc('Loyalty Program', {'loyalty_program_name': 'Test Multiple Loyalty'}).name
@@ -92,6 +94,7 @@
def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points'''
+ frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si = create_sales_invoice_record()
si.insert()
@@ -106,6 +109,7 @@
self.assertEqual(True, (lpe is None))
def test_sales_invoice_return(self):
+ frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
# create a new sales invoice
si_original = create_sales_invoice_record(2)
si_original.conversion_rate = flt(1)
@@ -149,8 +153,8 @@
""", self.name)
return abs(flt(returned_amount[0][0])) if returned_amount else 0
- lp_details = get_loyalty_program_details(self.customer, company=self.company,
- loyalty_program=self.loyalty_program, expiry_date=self.posting_date)
+ lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
+ loyalty_program=self.loyalty_program, expiry_date=self.posting_date, include_expired_entry=True)
if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
returned_amount = get_returned_amount()
@@ -167,6 +171,7 @@
"company": '_Test Company',
"due_date": today(),
"posting_date": today(),
+ "currency": "INR",
"taxes_and_charges": "",
"debit_to": "Debtors - _TC",
"taxes": [],
@@ -174,6 +179,7 @@
'doctype': 'Sales Invoice Item',
'item_code': frappe.get_doc('Item', {'item_name': 'Loyal Item'}).name,
'qty': qty,
+ "rate": 10000,
'income_account': 'Sales - _TC',
'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC'
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c981d08..456acba 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -7,7 +7,7 @@
from frappe import _, scrub, ValidationError
from frappe.utils import flt, comma_or, nowdate, getdate
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
-from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details
+from erpnext.accounts.party import get_party_account
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
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 9a9fe20..14f7891 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -242,6 +242,9 @@
price_list: this.frm.doc.buying_price_list
}, function() {
me.apply_pricing_rule();
+
+ me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
+ me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
})
},
@@ -493,6 +496,10 @@
},
onload: function(frm) {
+ if(frm.doc.__onload && !frm.doc.__onload.supplier_tds) {
+ me.frm.set_df_property("apply_tds", "read_only", 1);
+ }
+
$.each(["warehouse", "rejected_warehouse"], function(i, field) {
frm.set_query(field, "items", function() {
return {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 6eb629c..60ccf1c 100755
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -288,6 +288,39 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "default": "",
+ "fieldname": "apply_tds",
+ "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": "Apply Tax Withholding Amount",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 1,
+ "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_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -4606,5 +4639,6 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index c469a02..f343165 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -8,7 +8,7 @@
import frappe.defaults
from erpnext.controllers.buying_controller import BuyingController
-from erpnext.accounts.party import get_party_account, get_due_date, get_patry_tax_withholding_details
+from erpnext.accounts.party import get_party_account, get_due_date
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
@@ -21,6 +21,7 @@
from six import iteritems
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_invoice,\
unlink_inter_company_invoice
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@@ -42,6 +43,10 @@
'overflow_type': 'billing'
}]
+ def onload(self):
+ supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
+ self.set_onload("supplier_tds", supplier_tds)
+
def before_save(self):
if not self.on_hold:
self.release_date = ''
@@ -54,7 +59,10 @@
self.is_opening = 'No'
self.validate_posting_time()
+
+ # apply tax withholding only if checked and applicable
self.set_tax_withholding()
+
super(PurchaseInvoice, self).validate()
if not self.is_return:
@@ -768,14 +776,18 @@
self.db_set('release_date', None)
def set_tax_withholding(self):
- 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'])
+ if not self.apply_tds:
+ return
+
+ tax_withholding_details = get_party_tax_withholding_details(self)
+ accounts = []
+ for d in self.taxes:
+ if d.account_head == tax_withholding_details.get("account_head"):
+ d.update(tax_withholding_details)
+ accounts.append(d.account_head)
+
+ if not accounts or tax_withholding_details.get("account_head") not in accounts:
+ self.append("taxes", tax_withholding_details)
@frappe.whitelist()
def make_debit_note(source_name, target_doc=None):
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 2c4125f..f559fc0 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -22,7 +22,7 @@
from erpnext.setup.doctype.company.company import update_company_current_month_sales
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
- get_loyalty_program_details, get_loyalty_details, validate_loyalty_points
+ get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from six import iteritems
@@ -973,12 +973,14 @@
# collection of the loyalty points, create the ledger entry for that.
def make_loyalty_point_entry(self):
- lp_details = get_loyalty_program_details(self.customer, company=self.company,
- loyalty_program=self.loyalty_program, expiry_date=self.posting_date)
+ returned_amount = self.get_returned_amount()
+ current_amount = flt(self.grand_total) - cint(self.loyalty_amount)
+ eligible_amount = current_amount - returned_amount
+ lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
+ current_transaction_amount=current_amount, loyalty_program=self.loyalty_program,
+ expiry_date=self.posting_date, include_expired_entry=True)
if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
- returned_amount = self.get_returned_amount()
- eligible_amount = flt(self.grand_total) - cint(self.loyalty_amount) - returned_amount
points_earned = cint(eligible_amount/lp_details.collection_factor)
doc = frappe.get_doc({
"doctype": "Loyalty Point Entry",
@@ -994,7 +996,7 @@
})
doc.flags.ignore_permissions = 1
doc.save()
- frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
+ self.set_loyalty_program_tier()
# valdite the redemption and then delete the loyalty points earned on cancel of the invoice
def delete_loyalty_point_entry(self):
@@ -1009,9 +1011,12 @@
else:
frappe.db.sql('''delete from `tabLoyalty Point Entry` where sales_invoice=%s''', (self.name))
# Set loyalty program
- lp_details = get_loyalty_program_details(self.customer, company=self.company,
- loyalty_program=self.loyalty_program, expiry_date=self.posting_date)
- frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
+ self.set_loyalty_program_tier()
+
+ def set_loyalty_program_tier(self):
+ lp_details = get_loyalty_program_details_with_points(self.customer, company=self.company,
+ loyalty_program=self.loyalty_program, include_expired_entry=True)
+ frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
def get_returned_amount(self):
returned_amount = frappe.db.sql("""
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 8edaf01..f9160e2 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
@@ -15,51 +15,21 @@
"fields": [
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
- "fieldname": "percent_of_tax_withheld",
- "fieldtype": "Float",
+ "fieldname": "category_name",
+ "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
- "in_list_view": 1,
+ "in_list_view": 1,
"in_standard_filter": 0,
- "label": "Percent of Tax Withheld",
- "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
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "threshold",
- "fieldtype": "Currency",
- "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": "Threshold",
+ "label": "Category Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -77,100 +47,74 @@
},
{
"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,
+ "fieldname": "section_break_8",
+ "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": "Tax Withholding Rates",
+ "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": "book_on_invoice",
- "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": "Book on Invoice",
- "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,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "rates",
+ "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": "Rates",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Tax Withholding Rate",
+ "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
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "withhold_cumulative_tax_amount",
- "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": "Withhold Cumulative Tax Amount On First Invoice After 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
- },
- {
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
@@ -182,6 +126,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
+ "label": "Account Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -199,6 +144,7 @@
},
{
"allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -240,7 +186,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-05-16 13:57:52.489773",
+ "modified": "2018-07-17 22:53:26.193179",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Category",
@@ -305,12 +251,13 @@
"write": 1
}
],
- "quick_entry": 1,
+ "quick_entry": 0,
"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_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
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 90c7d50..0dc4ecf 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -4,7 +4,115 @@
from __future__ import unicode_literals
import frappe
+from frappe import _
from frappe.model.document import Document
+from frappe.utils import flt
+from erpnext.accounts.utils import get_fiscal_year
class TaxWithholdingCategory(Document):
- pass
\ No newline at end of file
+ pass
+
+def get_party_tax_withholding_details(ref_doc):
+ tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
+ if not tax_withholding_category:
+ return
+
+ fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
+ tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
+ tds_amount = get_tds_amount(ref_doc, tax_details, fy)
+ tax_row = get_tax_row(tax_details, tds_amount)
+ return tax_row
+
+def get_tax_withholding_details(tax_withholding_category, fiscal_year, company):
+ tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
+
+ tax_rate_detail = get_tax_withholding_rates(tax_withholding, fiscal_year)
+
+ 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,
+ "threshold": tax_rate_detail.single_threshold,
+ "cumulative_threshold": tax_rate_detail.cumulative_threshold,
+ "description": tax_withholding.category_name
+ })
+
+def get_tax_withholding_rates(tax_withholding, fiscal_year):
+ # returns the row that matches with the fiscal year from posting date
+ for rate in tax_withholding.rates:
+ if rate.fiscal_year == fiscal_year:
+ return rate
+
+ frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
+
+def get_tax_row(tax_details, tds_amount):
+ return {
+ "category": "Total",
+ "add_deduct_tax": "Deduct",
+ "charge_type": "Actual",
+ "account_head": tax_details.account_head,
+ "description": tax_details.description,
+ "tax_amount": tds_amount
+ }
+
+def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
+ fiscal_year, year_start_date, year_end_date = fiscal_year_details
+ tds_amount = 0
+
+ def _get_tds():
+ tds_amount = 0
+ if not tax_details.threshold or ref_doc.net_total >= tax_details.threshold:
+ tds_amount = ref_doc.net_total * tax_details.rate / 100
+ return tds_amount
+
+ if tax_details.cumulative_threshold:
+ entries = frappe.db.sql("""
+ select voucher_no, credit
+ from `tabGL Entry`
+ where party=%s and fiscal_year=%s and credit > 0
+ """, (ref_doc.supplier, fiscal_year), as_dict=1)
+
+ supplier_credit_amount = flt(sum([d.credit for d in entries]))
+
+ vouchers = [d.voucher_no for d in entries]
+ vouchers += get_advance_vouchers(ref_doc.supplier, fiscal_year)
+
+ tds_deducted = 0
+ if vouchers:
+ tds_deducted = flt(frappe.db.sql("""
+ select sum(credit)
+ from `tabGL Entry`
+ where account=%s and fiscal_year=%s and credit > 0
+ and voucher_no in ({0})
+ """.format(', '.join(["'%s'" % d for d in vouchers])),
+ (tax_details.account_head, fiscal_year))[0][0])
+
+ debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date)
+
+ total_invoiced_amount = supplier_credit_amount + tds_deducted \
+ + flt(ref_doc.net_total) - debit_note_amount
+ if total_invoiced_amount >= tax_details.cumulative_threshold:
+ total_applicable_tds = total_invoiced_amount * tax_details.rate / 100
+ tds_amount = min(total_applicable_tds - tds_deducted, ref_doc.net_total)
+ else:
+ tds_amount = _get_tds()
+ else:
+ tds_amount = _get_tds()
+
+ return tds_amount
+
+def get_advance_vouchers(supplier, fiscal_year):
+ return frappe.db.sql_list("""
+ select distinct voucher_no
+ from `tabGL Entry`
+ where party=%s and fiscal_year=%s and debit > 0
+ """, (supplier, fiscal_year))
+
+def get_debit_note_amount(supplier, year_start_date, year_end_date):
+ return flt(frappe.db.sql("""
+ select abs(sum(net_total))
+ from `tabPurchase Invoice`
+ where supplier=%s and is_return=1 and docstatus=1
+ and posting_date between %s and %s
+ """, (supplier, year_start_date, year_end_date)))
\ No newline at end of file
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 7d99011..20e1746 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
@@ -5,6 +5,107 @@
import frappe
import unittest
+from frappe.utils import today
+
+test_dependencies = ["Supplier Group"]
class TestTaxWithholdingCategory(unittest.TestCase):
- pass
+ @classmethod
+ def setUpClass(self):
+ # create relevant supplier, etc
+ create_records()
+
+ def test_single_threshold_tds(self):
+ frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194D - Individual")
+ pi = create_purchase_invoice()
+ pi.submit()
+
+ self.assertEqual(pi.taxes_and_charges_deducted, 800)
+ self.assertEqual(pi.grand_total, 15200)
+
+ # check gl entry for the purchase invoice
+ gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"])
+ self.assertEqual(len(gl_entries), 3)
+ for d in gl_entries:
+ if d.account == pi.credit_to:
+ self.assertEqual(d.credit, 15200)
+ elif d.account == pi.items[0].get("expense_account"):
+ self.assertEqual(d.debit, 16000)
+ elif d.account == pi.taxes[0].get("account_head"):
+ self.assertEqual(d.credit, 800)
+ else:
+ raise ValueError("Account head does not match.")
+
+ # delete purchase invoice to avoid it interefering in other tests
+ pi.cancel()
+ frappe.delete_doc('Purchase Invoice', pi.name)
+
+ def test_cumulative_threshold_tds(self):
+ frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194C - Individual")
+ invoices = []
+
+ # create invoices for lower than single threshold tax rate
+ for _ in xrange(6):
+ pi = create_purchase_invoice()
+ pi.submit()
+ invoices.append(pi)
+
+ # create another invoice whose total when added to previously created invoice,
+ # surpasses cumulative threshhold
+ pi = create_purchase_invoice()
+ pi.submit()
+
+ # assert equal tax deduction on total invoice amount uptil now
+ self.assertEqual(pi.taxes_and_charges_deducted, 1120)
+ self.assertEqual(pi.grand_total, 14880)
+ invoices.append(pi)
+
+ # delete invoices to avoid clashing
+ for d in invoices:
+ d.cancel()
+ frappe.delete_doc("Purchase Invoice", d.name)
+
+def create_purchase_invoice(qty=1):
+ # return sales invoice doc object
+ item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
+ pi = frappe.get_doc({
+ "doctype": "Purchase Invoice",
+ "posting_date": today(),
+ "apply_tds": 1,
+ "supplier": frappe.get_doc('Supplier', {"supplier_name": "Test TDS Supplier"}).name,
+ "company": '_Test Company',
+ "taxes_and_charges": "",
+ "currency": "INR",
+ "credit_to": "Creditors - _TC",
+ "taxes": [],
+ "items": [{
+ 'doctype': 'Purchase Invoice Item',
+ 'item_code': item.name,
+ 'qty': qty,
+ 'rate': 16000,
+ 'cost_center': 'Main - _TC',
+ 'expense_account': 'Stock Received But Not Billed - _TC'
+ }]
+ })
+
+ pi.save()
+ return pi
+
+def create_records():
+ # create a new supplier
+ frappe.get_doc({
+ "supplier_group": "_Test Supplier Group",
+ "supplier_name": "Test TDS Supplier",
+ "doctype": "Supplier",
+ "tax_withholding_category": "TDS - 194D - Individual"
+ }).insert()
+
+ # create an item
+ frappe.get_doc({
+ "doctype": "Item",
+ "item_code": "TDS Item",
+ "item_name": "TDS Item",
+ "item_group": "All Item Groups",
+ "company": "_Test Company",
+ "is_stock_item": 0,
+ }).insert()
\ No newline at end of file
diff --git a/erpnext/buying/doctype/party_tax_withholding_config/__init__.py b/erpnext/accounts/doctype/tax_withholding_rate/__init__.py
similarity index 100%
rename from erpnext/buying/doctype/party_tax_withholding_config/__init__.py
rename to erpnext/accounts/doctype/tax_withholding_rate/__init__.py
diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
similarity index 70%
rename from erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json
rename to erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
index d2583e5..1e8194a 100644
--- a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json
+++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
@@ -4,7 +4,7 @@
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
- "creation": "2018-05-11 13:32:33.825307",
+ "creation": "2018-07-17 16:53:13.716665",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
@@ -18,8 +18,8 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "columns": 0,
- "fieldname": "tax_withholding_category",
+ "columns": 2,
+ "fieldname": "fiscal_year",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -28,10 +28,10 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
- "label": "Tax Withholding Category",
+ "label": "Fiscal Year",
"length": 0,
"no_copy": 0,
- "options": "Tax Withholding Category",
+ "options": "Fiscal Year",
"permlevel": 0,
"precision": "",
"print_hide": 0,
@@ -39,7 +39,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
- "reqd": 0,
+ "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -51,41 +51,8 @@
"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_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_from": "tax_withholding_category.percent_of_tax_withheld",
- "fieldname": "applicable_percent",
+ "columns": 2,
+ "fieldname": "tax_withholding_rate",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -94,7 +61,38 @@
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
- "label": "Applicable Percent",
+ "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
+ },
+ {
+ "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,
@@ -116,17 +114,49 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
- "columns": 0,
- "fieldname": "certificate_received",
- "fieldtype": "Check",
+ "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": 0,
+ "in_list_view": 1,
"in_standard_filter": 0,
- "label": "Certificate Received",
+ "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
+ },
+ {
+ "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,
@@ -153,10 +183,10 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
- "modified": "2018-06-18 22:38:38.638721",
+ "modified": "2018-07-17 17:13:09.819580",
"modified_by": "Administrator",
- "module": "Buying",
- "name": "Party Tax Withholding Config",
+ "module": "Accounts",
+ "name": "Tax Withholding Rate",
"name_case": "",
"owner": "Administrator",
"permissions": [],
diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.py
similarity index 84%
rename from erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py
rename to erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.py
index bec7e83..6e32abe 100644
--- a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py
+++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.py
@@ -6,5 +6,5 @@
import frappe
from frappe.model.document import Document
-class PartyTaxWithholdingConfig(Document):
+class TaxWithholdingRate(Document):
pass
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index d7fe123..cc90c74 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -65,6 +65,10 @@
"allocated_percentage": d.allocated_percentage or None
} for d in party.get("sales_team")]
+ # supplier tax withholding category
+ if party_type == "Supplier" and party:
+ out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
+
return out
def set_address_details(out, party, party_type, doctype=None, company=None):
@@ -507,57 +511,3 @@
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 = []
- 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_percent
- })
-
- 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)
-
- if tax_withholding.book_on_invoice and ref_doc.doctype=='Purchase Invoice' \
- or 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/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 1d342cd..45901dc 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -186,6 +186,39 @@
"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
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"default": "0",
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
@@ -1159,39 +1192,6 @@
"allow_in_quick_entry": 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_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "supplier_details",
"columns": 0,
@@ -1363,7 +1363,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-06-26 13:44:53.124637",
+ "modified": "2018-07-17 12:14:59.417939",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
@@ -1512,5 +1512,6 @@
"sort_order": "ASC",
"title_field": "supplier_name",
"track_changes": 1,
- "track_seen": 0
+ "track_seen": 0,
+ "track_views": 0
}
\ No newline at end of file
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 378a803..3a5a062 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -42,6 +42,7 @@
args: args,
callback: function(r) {
if(r.message) {
+ frm.supplier_tds = r.message.supplier_tds;
frm.updating_party_details = true;
frappe.run_serially([
() => frm.set_value(r.message),
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 7bb09e9..b16affa 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -7,6 +7,8 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.permissions import add_permission
from erpnext.regional.india import states
+from erpnext.accounts.utils import get_fiscal_year
+from frappe.utils import today
def setup(company=None, patch=True):
make_custom_fields()
@@ -257,8 +259,10 @@
doc.insert()
except frappe.NameError:
pass
+ except frappe.DuplicateEntryError:
+ pass
- # create tds fixtures
+ # create records for Tax Withholding Category
set_tax_withholding_category(company)
def set_salary_components(docs):
@@ -279,19 +283,24 @@
if company and tds_account:
accounts = [dict(company=company, account=tds_account)]
- tds = frappe.get_doc({
- 'doctype': 'Tax Withholding Category', 'name': 'TDS',
- 'percent_of_tax_withheld': 10,'threshold': 150000, 'book_on_invoice': 1,
- 'withhold_cumulative_tax_amount': 0, 'accounts': accounts
- })
+ fiscal_year = get_fiscal_year(today(), company=accounts[0].get('company'))[0]
+ docs = get_tds_details(accounts, fiscal_year)
- try:
- tds.flags.ignore_permissions = True
- tds.insert()
- except frappe.DuplicateEntryError:
- tds = frappe.get_doc("Tax Withholding Category", tds.get("name"))
- tds.append("accounts", accounts[0])
- tds.save()
+ for d in docs:
+ try:
+ doc = frappe.get_doc(d)
+ doc.flags.ignore_permissions = True
+ doc.insert()
+ except frappe.DuplicateEntryError:
+ doc = frappe.get_doc("Tax Withholding Category", d.get("name"))
+ doc.append("accounts", accounts[0])
+
+ # if fiscal year don't match with any of the already entered data, append rate row
+ fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
+ if not fy_exist:
+ doc.append("rates", d.get('rates')[0])
+
+ doc.save()
def set_tds_account(docs, company):
abbr = frappe.get_value("Company", company, "abbr")
@@ -301,3 +310,148 @@
"parent_account": "Duties and Taxes - {0}".format(abbr), "company": company
}
])
+
+def get_tds_details(accounts, fiscal_year):
+ # bootstrap default tax withholding sections
+ return [
+ dict(name="TDS - 194C - Company",
+ category_name="Payment to Contractors (Single / Aggregate)",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
+ "single_threshold": 30000, "cumulative_threshold": 100000}]),
+ dict(name="TDS - 194C - Individual",
+ category_name="Payment to Contractors (Single / Aggregate)",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
+ "single_threshold": 30000, "cumulative_threshold": 100000}]),
+ dict(name="TDS - 194C - No PAN / Invalid PAN",
+ category_name="Payment to Contractors (Single / Aggregate)",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 30000, "cumulative_threshold": 100000}]),
+ dict(name="TDS - 194D - Company",
+ category_name="Insurance Commission",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194D - Company Assessee",
+ category_name="Insurance Commission",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194D - Individual",
+ category_name="Insurance Commission",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194D - No PAN / Invalid PAN",
+ category_name="Insurance Commission",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194DA - Company",
+ category_name="Non-exempt payments made under a life insurance policy",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
+ "single_threshold": 100000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194DA - Individual",
+ category_name="Non-exempt payments made under a life insurance policy",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1,
+ "single_threshold": 100000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194DA - No PAN / Invalid PAN",
+ category_name="Non-exempt payments made under a life insurance policy",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 100000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194H - Company",
+ category_name="Commission / Brokerage",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194H - Individual",
+ category_name="Commission / Brokerage",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194H - No PAN / Invalid PAN",
+ category_name="Commission / Brokerage",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 15000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent - Company",
+ category_name="Rent",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent - Individual",
+ category_name="Rent",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent - No PAN / Invalid PAN",
+ category_name="Rent",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent/Machinery - Company",
+ category_name="Rent-Plant / Machinery",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent/Machinery - Individual",
+ category_name="Rent-Plant / Machinery",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194I - Rent/Machinery - No PAN / Invalid PAN",
+ category_name="Rent-Plant / Machinery",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 180000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Professional Fees - Company",
+ category_name="Professional Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 30000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Professional Fees - Individual",
+ category_name="Professional Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 30000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Professional Fees - No PAN / Invalid PAN",
+ category_name="Professional Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 30000, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Director Fees - Company",
+ category_name="Director Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 0, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Director Fees - Individual",
+ category_name="Director Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 0, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194J - Director Fees - No PAN / Invalid PAN",
+ category_name="Director Fees",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 0, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194 - Dividends - Company",
+ category_name="Dividends",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 2500, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194 - Dividends - Individual",
+ category_name="Dividends",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10,
+ "single_threshold": 2500, "cumulative_threshold": 0}]),
+ dict(name="TDS - 194 - Dividends - No PAN / Invalid PAN",
+ category_name="Dividends",
+ doctype="Tax Withholding Category", accounts=accounts,
+ rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20,
+ "single_threshold": 2500, "cumulative_threshold": 0}])
+ ]
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index 4ad8af8..c3260ab 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -8,6 +8,7 @@
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import get_charts_for_country
test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component"]
+test_dependencies = ["Fiscal Year"]
test_records = frappe.get_test_records('Company')
class TestCompany(unittest.TestCase):
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 70bd702..34985d9 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -36,8 +36,8 @@
# check for the loyalty program of the customer
customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program")
if customer_loyalty_program:
- from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details
- loyalty_program_details = get_loyalty_program_details(context.doc.customer, customer_loyalty_program)
+ from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
+ loyalty_program_details = get_loyalty_program_details_with_points(context.doc.customer, customer_loyalty_program)
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
def get_attachments(dt, dn):