Merge pull request #14108 from ESS-LLP/tax_deduction

Variable tax deduction
diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index 9dee848..c5691f7 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -145,6 +145,37 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "fieldname": "total_exemption_amount", 
+   "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": "Total Exemption Amount", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 1, 
+   "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": "amended_from", 
    "fieldtype": "Link", 
    "hidden": 0, 
@@ -243,7 +274,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-05-15 16:16:46.075493", 
+ "modified": "2018-05-16 19:03:57.624215", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Declaration", 
diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
index 52746d4..22e1638 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
+++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
@@ -19,3 +19,6 @@
 							"docstatus": 1}):
 			frappe.throw(_("Tax Declaration of {0} for period {1} already submitted.")\
 			.format(self.employee, self.payroll_period), frappe.DocstatusTransitionError)
+		self.total_exemption_amount = 0
+		for item in self.declarations:
+			self.total_exemption_amount += item.amount
diff --git a/erpnext/hr/doctype/salary_component/salary_component.py b/erpnext/hr/doctype/salary_component/salary_component.py
index 9108f31..beffaec 100644
--- a/erpnext/hr/doctype/salary_component/salary_component.py
+++ b/erpnext/hr/doctype/salary_component/salary_component.py
@@ -18,4 +18,13 @@
 
 		self.salary_component_abbr = self.salary_component_abbr.strip()
 		self.salary_component_abbr = append_number_if_name_exists('Salary Component', self.salary_component_abbr,
-			'salary_component_abbr', separator='_', filters={"name": ["!=", self.name]})
\ No newline at end of file
+			'salary_component_abbr', separator='_', filters={"name": ["!=", self.name]})
+
+	def calculate_tax(self, annual_earning):
+		taxable_amount = 0
+		for slab in self.taxable_salary_slabs:
+			if annual_earning > slab.from_amount and annual_earning < slab.to_amount:
+				taxable_amount += (annual_earning - slab.from_amount) * slab.percent_deduction *.01
+			elif annual_earning > slab.from_amount and annual_earning > slab.to_amount:
+				taxable_amount += (slab.to_amount - slab.from_amount) * slab.percent_deduction * .01
+		return taxable_amount
diff --git a/erpnext/hr/doctype/salary_detail/salary_detail.json b/erpnext/hr/doctype/salary_detail/salary_detail.json
index 01d0277..862728a 100644
--- a/erpnext/hr/doctype/salary_detail/salary_detail.json
+++ b/erpnext/hr/doctype/salary_detail/salary_detail.json
@@ -40,6 +40,7 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -72,6 +73,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -101,6 +103,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -132,6 +135,100 @@
    "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": "is_tax_applicable", 
+   "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 Tax Applicable", 
+   "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": "is_flexible_benefit", 
+   "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 Flexible Benefit", 
+   "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": "variable_based_on_taxable_salary", 
+   "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": "Variable Based On Taxable Salary", 
+   "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
   }, 
   {
@@ -161,6 +258,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -192,6 +290,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -225,6 +324,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -258,6 +358,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -290,6 +391,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -321,6 +423,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -351,6 +454,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -383,6 +487,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -413,6 +518,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -445,6 +551,7 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
+   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -458,7 +565,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2017-10-02 13:57:22.769751", 
+ "modified": "2018-05-16 22:42:59.974450", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Salary Detail", 
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 984a78c..53e6aa4 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -13,6 +13,7 @@
 from erpnext.utilities.transaction_base import TransactionBase
 from frappe.utils.background_jobs import enqueue
 from erpnext.hr.doctype.additional_salary_component.additional_salary_component import get_additional_salary_component
+from erpnext.hr.utils import get_payroll_period
 
 class SalarySlip(TransactionBase):
 	def autoname(self):
@@ -58,6 +59,10 @@
 				amount = self.eval_condition_and_formula(struct_row, data)
 				if amount and struct_row.statistical_component == 0:
 					self.update_component_row(struct_row, amount, key)
+				if key=="deductions" and struct_row.variable_based_on_taxable_salary:
+					tax_row, amount = self.calculate_pro_rata_tax(struct_row.salary_component)
+					if tax_row and amount:
+						self.update_component_row(frappe._dict(tax_row), amount, key)
 
 		additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date)
 		if additional_components:
@@ -464,6 +469,52 @@
 			status = "Cancelled"
 		return status
 
+	def calculate_pro_rata_tax(self, salary_component):
+		# Calculate total tax payable earnings
+		tax_applicable_components = []
+		for earning in self._salary_structure_doc.earnings:
+			#all tax applicable earnings which are not flexi
+			if earning.is_tax_applicable and not earning.is_flexible_benefit:
+				tax_applicable_components.append(earning.salary_component)
+		total_taxable_earning = 0
+		for earning in self.earnings:
+			if earning.salary_component in tax_applicable_components:
+				total_taxable_earning += earning.amount
+
+		# Get payroll period, prorata frequency
+		days = date_diff(self.end_date, self.start_date) + 1
+		payroll_period = get_payroll_period(self.start_date, self.end_date, self.company)
+		if not payroll_period:
+			frappe.throw(_("Start and end dates not in a valid Payroll Period"))
+		total_days = date_diff(payroll_period.end_date, payroll_period.start_date) + 1
+		prorata_frequency = flt(total_days)/flt(days)
+		annual_earning = total_taxable_earning * prorata_frequency
+
+		# Calculate total exemption declaration
+		exemption_amount = 0
+		if frappe.db.exists("Employee Tax Exemption Declaration", {"employee": self.employee,
+		"payroll_period": payroll_period.name, "docstatus": 1}):
+			exemption_amount = frappe.db.get_value("Employee Tax Exemption Declaration",
+				{"employee": self.employee, "payroll_period": payroll_period.name, "docstatus": 1}, #fix period
+				"total_exemption_amount")
+		annual_earning = annual_earning - exemption_amount
+
+		# Get tax calc by component
+		component = frappe.get_doc("Salary Component", salary_component)
+		annual_tax = component.calculate_tax(annual_earning)
+
+		# Calc prorata tax
+		pro_rata_tax = annual_tax/prorata_frequency
+
+		# Data for update_component_row
+		struct_row = {}
+		struct_row['depends_on_lwp'] = 0
+		struct_row['salary_component'] = component.name
+		struct_row['abbr'] = component.salary_component_abbr
+		struct_row['do_not_include_in_total'] = 0
+
+		return struct_row, pro_rata_tax
+
 def unlink_ref_doc_from_salary_slip(ref_no):
 	linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip`
 	where journal_entry=%s and docstatus < 2""", (ref_no))
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index ca92234..8fd51d9 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -201,17 +201,20 @@
 				callback: function(data) {
 					if(data.message){
 						var result = data.message;
-						frappe.model.set_value(cdt, cdn, 'condition',result.condition);
-						frappe.model.set_value(cdt, cdn, 'amount_based_on_formula',result.amount_based_on_formula);
+						frappe.model.set_value(cdt, cdn, 'condition', result.condition);
+						frappe.model.set_value(cdt, cdn, 'amount_based_on_formula', result.amount_based_on_formula);
 						if(result.amount_based_on_formula == 1){
-							frappe.model.set_value(cdt, cdn, 'formula',result.formula);
+							frappe.model.set_value(cdt, cdn, 'formula', result.formula);
 						}
 						else{
-							frappe.model.set_value(cdt, cdn, 'amount',result.amount);
+							frappe.model.set_value(cdt, cdn, 'amount', result.amount);
 						}
-						frappe.model.set_value(cdt, cdn, 'statistical_component',result.statistical_component);
-						frappe.model.set_value(cdt, cdn, 'depends_on_lwp',result.depends_on_lwp);
-						frappe.model.set_value(cdt, cdn, 'do_not_include_in_total',result.do_not_include_in_total);
+						frappe.model.set_value(cdt, cdn, 'statistical_component', result.statistical_component);
+						frappe.model.set_value(cdt, cdn, 'depends_on_lwp', result.depends_on_lwp);
+						frappe.model.set_value(cdt, cdn, 'do_not_include_in_total', result.do_not_include_in_total);
+						frappe.model.set_value(cdt, cdn, 'variable_based_on_taxable_salary', result.variable_based_on_taxable_salary);
+						frappe.model.set_value(cdt, cdn, 'is_tax_applicable', result.is_tax_applicable);
+						frappe.model.set_value(cdt, cdn, 'is_flexible_benefit', result.is_flexible_benefit);
 						refresh_field("earnings");
 						refresh_field("deductions");
 					}
diff --git a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json
index bd0ec6b..8e6c1d8 100644
--- a/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json
+++ b/erpnext/hr/doctype/taxable_salary_slab/taxable_salary_slab.json
@@ -50,7 +50,7 @@
    "collapsible": 0, 
    "columns": 0, 
    "fieldname": "to_amount", 
-   "fieldtype": "Data", 
+   "fieldtype": "Currency", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -147,7 +147,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-04-13 20:09:36.675987", 
+ "modified": "2018-05-16 18:18:23.802576", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Taxable Salary Slab", 
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index df720c4..20fe666 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -234,3 +234,10 @@
 
 	if leave_period:
 		return leave_period
+
+def get_payroll_period(from_date, to_date, company):
+	payroll_period = frappe.db.sql("""select pp.name, pd.start_date, pd.end_date from
+		`tabPayroll Period Date` pd join `tabPayroll Period` pp on
+		pd.parent=pp.name where pd.start_date<=%s and pd.end_date>= %s
+		and pp.company=%s""", (from_date, to_date, company), as_dict=1)
+	return payroll_period[0] if payroll_period else None