Tax Exemption Declaration - HRA Calculation
diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
index 23d158c..fb8c81a 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
+++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
@@ -2,6 +2,11 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Employee Tax Exemption Declaration', {
+	refresh: function(frm){
+		if(frm.doc.__islocal){
+			frm.set_df_property('hra_declaration_section', 'hidden', 1);
+		}
+	},
 	setup: function(frm) {
 		frm.set_query('employee', function() {
 			return {
@@ -36,10 +41,45 @@
 		});
 	},
 	employee: function(frm){
-		if(frm.doc.employee){
-			frm.add_fetch('employee', 'company', 'company');
-		}else{
-			frm.set_value('company', '');
+		frm.trigger('set_null_value');
+	},
+	company: function(frm) {
+		if(frm.doc.company){
+			frappe.call({
+				method: "frappe.client.get_value",
+				args: {
+					doctype: "Company",
+					filters: {"name": frm.doc.company},
+					fieldname: "hra_component"
+				},
+				callback: function(r){
+					if(r.message.hra_component){
+						frm.set_df_property('hra_declaration_section', 'hidden', 0);
+					}
+				}
+			});
 		}
+	},
+	monthly_house_rent: function(frm) {
+		frm.trigger("calculate_hra_component");
+	},
+	rented_in_metro_city: function(frm) {
+		frm.trigger("calculate_hra_component");
+	},
+	calculate_hra_component: function(frm) {
+		frappe.call({
+			method: "calculate_hra_component",
+			doc: frm.doc,
+			callback: function(r) {
+				frm.refresh_fields();
+			}
+		});
+	},
+	set_null_value(frm){
+		let fields = ['salary_structure_hra', 'monthly_house_rent','annual_hra', 'monthly_hra',
+		'total_exemption_amount', 'payroll_period'];
+		fields.forEach(function(field) {
+			frm.set_value(field, '');
+		});
 	}
 });
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 f107b06..b984873 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
@@ -249,7 +249,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "section_break_4", 
+   "fieldname": "section_break_8", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -313,7 +313,8 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "hra_declaration", 
+   "depends_on": "", 
+   "fieldname": "hra_declaration_section", 
    "fieldtype": "Section Break", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -361,7 +362,7 @@
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 0, 
+   "read_only": 1, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -489,7 +490,7 @@
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 0, 
+   "read_only": 1, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -521,7 +522,7 @@
    "precision": "", 
    "print_hide": 0, 
    "print_hide_if_no_value": 0, 
-   "read_only": 0, 
+   "read_only": 1, 
    "remember_last_selected_value": 0, 
    "report_hide": 0, 
    "reqd": 0, 
@@ -541,7 +542,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-05-29 12:41:27.446550", 
+ "modified": "2018-05-30 13:35:08.941961", 
  "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 22e1638..b2d87bc 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
@@ -6,11 +6,19 @@
 import frappe
 from frappe.model.document import Document
 from frappe import _
-from erpnext.hr.utils import validate_tax_declaration
+from frappe.utils import getdate, flt
+from erpnext.hr.utils import validate_tax_declaration, get_salary_assignment
+from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
 
 class EmployeeTaxExemptionDeclaration(Document):
 	def validate(self):
 		validate_tax_declaration(self.declarations)
+		self.calculate_hra_component()
+		self.total_exemption_amount = 0
+		for item in self.declarations:
+			self.total_exemption_amount += item.amount
+		if self.annual_hra:
+			self.total_exemption_amount += self.annual_hra
 
 	def before_submit(self):
 		if frappe.db.exists({"doctype": "Employee Tax Exemption Declaration",
@@ -19,6 +27,53 @@
 							"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
+
+	def calculate_hra_component(self):
+		hra_component = frappe.db.get_value("Company", self.company, "hra_component")
+		if hra_component:
+			assignment = get_salary_assignment(self.employee, getdate())
+			if assignment and frappe.db.exists("Salary Detail", {
+				"parent": assignment.salary_structure,
+				"salary_component": hra_component, "parentfield": "earnings"}):
+				hra_amount = self.get_hra_from_salary_slip(assignment.salary_structure, hra_component)
+				if hra_amount:
+					self.salary_structure_hra = hra_amount
+					if self.monthly_house_rent:
+						self.annual_hra, self.monthly_hra = 0, 0
+						annual_hra = self.calculate_eligible_hra_amount(assignment.salary_structure, assignment.base)
+						if annual_hra > 0:
+							self.annual_hra = annual_hra
+							self.monthly_hra = annual_hra / 12
+
+	def calculate_eligible_hra_amount(self, salary_structure, base):
+		# TODO make this configurable
+		exemptions = []
+		frequency = frappe.get_value("Salary Structure", salary_structure, "payroll_frequency")
+		# case 1: The actual amount allotted by the employer as the HRA.
+		exemptions.append(self.get_annual_component_pay(frequency, self.salary_structure_hra))
+		actual_annual_rent = self.monthly_house_rent * 12
+		annual_base = self.get_annual_component_pay(frequency, base)
+		# case 2: Actual rent paid less 10% of the basic salary.
+		exemptions.append(flt(actual_annual_rent) - flt(annual_base * 0.1))
+		# case 3: 50% of the basic salary, if the employee is staying in a metro city (40% for a non-metro city).
+		exemptions.append(annual_base * 0.5 if self.rented_in_metro_city else annual_base * 0.4)
+		# return minimum of 3 cases
+		return min(exemptions)
+
+	def get_annual_component_pay(self, frequency, amount):
+		if frequency == "Daily":
+			return amount * 365
+		elif frequency == "Weekly":
+			return amount * 52
+		elif frequency == "Fortnightly":
+			return amount * 26
+		elif frequency == "Monthly":
+			return amount * 12
+		elif frequency == "Bimonthly":
+			return amount * 6
+
+	def get_hra_from_salary_slip(self, salary_structure, hra_component):
+		salary_slip = make_salary_slip(salary_structure, employee=self.employee)
+		for earning in salary_slip.earnings:
+			if earning.salary_component == hra_component:
+				return earning.amount
diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json b/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
index 29df064..ebde4c9 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
+++ b/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json
@@ -14,6 +14,7 @@
  "fields": [
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -41,18 +42,19 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 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": "exemption_sub_category.exemption_category",
+   "fetch_from": "exemption_sub_category.exemption_category", 
    "fieldname": "exemption_category", 
-   "fieldtype": "Read Only", 
+   "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
    "ignore_xss_filter": 0, 
@@ -63,7 +65,7 @@
    "label": "Exemption Category", 
    "length": 0, 
    "no_copy": 0, 
-   "options": "", 
+   "options": "Employee Tax Exemption Category", 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
@@ -74,11 +76,12 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0,
+   "translatable": 0, 
    "unique": 0
   }, 
   {
    "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
    "allow_on_submit": 0, 
    "bold": 0, 
    "collapsible": 0, 
@@ -105,7 +108,7 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0,
+   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -119,7 +122,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-05-16 22:42:42.980630",
+ "modified": "2018-05-29 15:58:05.779031", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Declaration Category", 
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 20fe666..71a763c 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -241,3 +241,16 @@
 		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
+
+def get_salary_assignment(employee, date):
+	assignment = frappe.db.sql("""
+		select * from `tabSalary Structure Assignment`
+		where employee=%(employee)s
+		and docstatus = 1
+		and (
+			(%(on_date)s between from_date and ifnull(to_date, '2199-12-31'))
+		)""", {
+			'employee': employee,
+			'on_date': date,
+		}, as_dict=1)
+	return assignment[0] if assignment else None