Merge pull request #13982 from ESS-LLP/tax_exemption

Employee Tax Exemption Declaration, Proof Submission
diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py
index e1c589f..5da0fbc 100644
--- a/erpnext/config/hr.py
+++ b/erpnext/config/hr.py
@@ -25,10 +25,6 @@
 				},
 				{
 					"type": "doctype",
-					"name": "Attendance Request",
-				},
-				{
-					"type": "doctype",
 					"name": "Upload Attendance",
 					"hide_count": True
 				}
@@ -135,6 +131,10 @@
 					"type": "doctype",
 					"name": "Employee Tax Exemption Category",
 				},
+				{
+					"type": "doctype",
+					"name": "Employee Tax Exemption Sub Category"
+				}
 			]
 		},
 		{
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 d204efc..b31bf0e 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,7 +2,38 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Employee Tax Exemption Declaration', {
-	refresh: function(frm) {
-
+	setup: function(frm) {
+		frm.set_query('employee', function() {
+			return {
+				filters: {
+					'status': "Active"
+				}
+			}
+		});
+		frm.set_query('payroll_period', function() {
+			if(frm.doc.employee && frm.doc.company){
+				return {
+					filters: {
+						'company': frm.doc.company
+					}
+				}
+			}else {
+				frappe.msgprint(__("Please select Employee"));
+			}
+		});
+		frm.set_query('exemption_sub_category', 'declarations', function() {
+			return {
+				filters: {
+					'is_active': 1
+				}
+			}
+		});
+	},
+	employee: function(frm){
+		if(frm.doc.employee){
+			frm.add_fetch('employee', 'company', 'company');
+		}else{
+			frm.set_value('company', '');
+		}
 	}
 });
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 ebfce2b..3c361ee 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
@@ -42,7 +42,37 @@
    "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": "company", 
+   "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": "Company", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Company", 
+   "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, 
    "unique": 0
   }, 
   {
@@ -72,7 +102,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -104,7 +133,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -135,7 +163,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -165,7 +192,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -197,7 +223,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -211,7 +236,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-04-13 19:23:54.363578", 
+ "modified": "2018-05-10 13:26:25.241545", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Declaration", 
@@ -220,6 +245,7 @@
  "permissions": [
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -239,6 +265,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -258,6 +285,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -277,6 +305,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
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 1a5f195..52746d4 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
@@ -5,6 +5,17 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe import _
+from erpnext.hr.utils import validate_tax_declaration
 
 class EmployeeTaxExemptionDeclaration(Document):
-	pass
+	def validate(self):
+		validate_tax_declaration(self.declarations)
+
+	def before_submit(self):
+		if frappe.db.exists({"doctype": "Employee Tax Exemption Declaration",
+							"employee": self.employee,
+							"payroll_period": self.payroll_period,
+							"docstatus": 1}):
+			frappe.throw(_("Tax Declaration of {0} for period {1} already submitted.")\
+			.format(self.employee, self.payroll_period), frappe.DocstatusTransitionError)
diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
index 48f561a..84970d8 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
+++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py
@@ -5,6 +5,110 @@
 
 import frappe
 import unittest
+from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee
 
 class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
-	pass
+	def setup(self):
+		make_employee("employee@taxexepmtion.com")
+		make_employee("employee1@taxexepmtion.com")
+		create_payroll_period()
+		create_exemption_category()
+		frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration`""")
+
+	def test_exemption_amount_greater_than_category_max(self):
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Declaration",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"declarations": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 150000)]
+		})
+		self.assertRaises(frappe.ValidationError, declaration.save)
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Declaration",
+			"payroll_period": "Test Payroll Period",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
+			"declarations": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 90000)]
+		})
+		self.assertTrue(declaration.save)
+
+	def test_duplicate_category_in_declaration(self):
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Declaration",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"declarations": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 100000),
+							dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 50000),
+							]
+		})
+		self.assertRaises(frappe.ValidationError, declaration.save)
+
+	def test_duplicate_submission_for_payroll_period(self):
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Declaration",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"declarations": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 100000),
+							dict(exemption_sub_category = "_Test1 Sub Category",
+							exemption_category = "_Test Category",
+							amount = 50000),
+							]
+		})
+		self.assertTrue(declaration.submit)
+		duplicate_declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Declaration",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"declarations": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 100000)
+							]
+		})
+		self.assertRaises(frappe.DocstatusTransitionError, duplicate_declaration.submit)
+		duplicate_declaration.employee = frappe.get_value("Employee", {"user_id":"employee1@taxexepmtion.com"}, "name")
+		self.assertTrue(duplicate_declaration.submit)
+
+def create_payroll_period():
+	if not frappe.db.exists("Payroll Period", "_Test Payroll Period"):
+		from datetime import date
+		payroll_period = frappe.get_doc(dict(
+			doctype = 'Payroll Period',
+			name = "_Test Payroll Period",
+			company = "_Test Company",
+			periods = [
+				dict(start_date = date(date.today().year, 1, 1),
+				end_date = date(date.today().year, 12, 31))
+			]
+		)).insert()
+
+def create_exemption_category():
+	if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"):
+		category = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Category",
+			"name": "_Test Category",
+			"deduction_component": "_Test Tax",
+			"max_amount": 100000
+		}).insert()
+	if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test Category"):
+		frappe.get_doc({
+			"doctype": "Employee Tax Exemption Sub Category",
+			"name": "_Test Sub Category",
+			"exemption_category": "_Test Category",
+			"max_amount": 100000
+		}).insert()
+	if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test Category"):
+		frappe.get_doc({
+			"doctype": "Employee Tax Exemption Sub Category",
+			"name": "_Test1 Sub Category",
+			"exemption_category": "_Test Category",
+			"max_amount": 50000
+		}).insert()
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 48a5dd0..bdaf9bf 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
@@ -18,7 +18,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "exemption_category", 
+   "fieldname": "exemption_sub_category", 
    "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -27,10 +27,10 @@
    "in_global_search": 0, 
    "in_list_view": 1, 
    "in_standard_filter": 0, 
-   "label": "Exemption Category", 
+   "label": "Exemption Sub Category", 
    "length": 0, 
    "no_copy": 0, 
-   "options": "Employee Tax Exemption Category", 
+   "options": "Employee Tax Exemption Sub Category", 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
@@ -41,7 +41,37 @@
    "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": "exemption_category", 
+   "fieldtype": "Read Only", 
+   "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": "Exemption Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "exemption_sub_category.exemption_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": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
    "unique": 0
   }, 
   {
@@ -72,7 +102,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -86,7 +115,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-04-13 19:24:18.076613", 
+ "modified": "2018-05-09 13:30:44.363393", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Declaration Category", 
diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
index d8036c4..99bec14 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
+++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
@@ -2,7 +2,38 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
-	refresh: function(frm) {
-
+	setup: function(frm) {
+		frm.set_query('employee', function() {
+			return {
+				filters: {
+					'status': "Active"
+				}
+			}
+		});
+		frm.set_query('payroll_period', function() {
+			if(frm.doc.employee && frm.doc.company){
+				return {
+					filters: {
+						'company': frm.doc.company
+					}
+				}
+			}else {
+				frappe.msgprint(__("Please select Employee"));
+			}
+		});
+		frm.set_query('exemption_sub_category', 'tax_exemption_proofs', function() {
+			return {
+				filters: {
+					'is_active': 1
+				}
+			}
+		});
+	},
+	employee: function(frm){
+		if(frm.doc.employee){
+			frm.add_fetch('employee', 'company', 'company');
+		}else{
+			frm.set_value('company', '');
+		}
 	}
 });
diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index 41d9681..51f23aa 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -42,7 +42,37 @@
    "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": "company", 
+   "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": "Company", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Company", 
+   "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, 
    "unique": 0
   }, 
   {
@@ -72,7 +102,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -104,7 +133,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -135,7 +163,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -165,7 +192,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -197,7 +223,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -228,7 +253,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -259,7 +283,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -290,7 +313,6 @@
    "reqd": 0, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -304,7 +326,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-04-13 19:21:59.969371", 
+ "modified": "2018-05-10 13:26:53.030547", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Proof Submission", 
@@ -313,6 +335,7 @@
  "permissions": [
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -332,6 +355,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -351,6 +375,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
@@ -370,6 +395,7 @@
   }, 
   {
    "amend": 1, 
+   "apply_user_permissions": 0, 
    "cancel": 1, 
    "create": 1, 
    "delete": 1, 
diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
index 1c31cc4..a0c003c 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
+++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
@@ -5,6 +5,17 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from frappe import _
+from erpnext.hr.utils import validate_tax_declaration
 
 class EmployeeTaxExemptionProofSubmission(Document):
-	pass
+	def validate(self):
+		validate_tax_declaration(self.tax_exemption_proofs)
+	#TODO: allow multiple?
+	# def before_submit(self):
+	# 	if frappe.db.exists({"doctype": "Employee Tax Exemption Proof Submission",
+	# 						"employee": self.employee,
+	# 						"payroll_period": self.payroll_period,
+	# 						"docstatus": 1}):
+	# 		frappe.throw(_("Proof Submission of {0} for period {1} already submitted.")\
+	# 		.format(self.employee, self.payroll_period), frappe.DocstatusTransitionError)
diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py
index 1dc090f..4b5777b 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py
+++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py
@@ -5,6 +5,50 @@
 
 import frappe
 import unittest
+from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_exemption_category, create_payroll_period
 
 class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase):
-	pass
+	def setup(self):
+		make_employee("employee@proofsubmission.com")
+		create_payroll_period()
+		create_exemption_category()
+		frappe.db.sql("""delete from `tabEmployee Tax Exemption Proof Submission`""")
+
+	def test_exemption_amount_lesser_than_category_max(self):
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Proof Submission",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category",
+							type_of_proof = "Test Proof",
+							exemption_category = "_Test Category",
+							amount = 150000)]
+		})
+		self.assertRaises(frappe.ValidationError, declaration.save)
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Proof Submission",
+			"payroll_period": "Test Payroll Period",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"),
+			"tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category",
+							type_of_proof = "Test Proof",
+							exemption_category = "_Test Category",
+							amount = 100000)]
+		})
+		self.assertTrue(declaration.save)
+		self.assertTrue(declaration.submit)
+
+	def test_duplicate_category_in_proof_submission(self):
+		declaration = frappe.get_doc({
+			"doctype": "Employee Tax Exemption Proof Submission",
+			"employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"),
+			"payroll_period": "Test Payroll Period",
+			"tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							type_of_proof = "Test Proof",
+							amount = 100000),
+							dict(exemption_sub_category = "_Test Sub Category",
+							exemption_category = "_Test Category",
+							amount = 50000),
+							]
+		})
+		self.assertRaises(frappe.ValidationError, declaration.save)
diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json b/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
index 509e425..3725900 100644
--- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
+++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json
@@ -18,7 +18,7 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
-   "fieldname": "exemption_category", 
+   "fieldname": "exemption_sub_category", 
    "fieldtype": "Link", 
    "hidden": 0, 
    "ignore_user_permissions": 0, 
@@ -27,10 +27,10 @@
    "in_global_search": 0, 
    "in_list_view": 1, 
    "in_standard_filter": 0, 
-   "label": "Exemption Category", 
+   "label": "Exemption Sub Category", 
    "length": 0, 
    "no_copy": 0, 
-   "options": "Employee Tax Exemption Category", 
+   "options": "Employee Tax Exemption Sub Category", 
    "permlevel": 0, 
    "precision": "", 
    "print_hide": 0, 
@@ -41,7 +41,37 @@
    "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": "exemption_category", 
+   "fieldtype": "Read Only", 
+   "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": "Exemption Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "exemption_sub_category.exemption_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": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
    "unique": 0
   }, 
   {
@@ -73,7 +103,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }, 
   {
@@ -104,7 +133,6 @@
    "reqd": 1, 
    "search_index": 0, 
    "set_only_once": 0, 
-   "translatable": 0, 
    "unique": 0
   }
  ], 
@@ -118,7 +146,7 @@
  "issingle": 0, 
  "istable": 1, 
  "max_attachments": 0, 
- "modified": "2018-04-13 17:19:03.006149", 
+ "modified": "2018-05-09 13:53:22.563316", 
  "modified_by": "Administrator", 
  "module": "HR", 
  "name": "Employee Tax Exemption Proof Submission Detail", 
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js
new file mode 100644
index 0000000..8a83a27
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Employee Tax Exemption Sub Category', {
+	refresh: function(frm) {
+
+	}
+});
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json
new file mode 100644
index 0000000..dc99785
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json
@@ -0,0 +1,194 @@
+{
+ "allow_copy": 0, 
+ "allow_guest_to_view": 0, 
+ "allow_import": 1, 
+ "allow_rename": 1, 
+ "autoname": "Prompt", 
+ "beta": 0, 
+ "creation": "2018-05-09 12:47:26.983095", 
+ "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": "exemption_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 Exemption Category", 
+   "length": 0, 
+   "no_copy": 0, 
+   "options": "Employee Tax Exemption 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": 1, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "max_amount", 
+   "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": "Max Amount", 
+   "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, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
+   "fieldname": "is_active", 
+   "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 Active", 
+   "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, 
+   "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": 0, 
+ "max_attachments": 0, 
+ "modified": "2018-05-09 13:25:01.595240", 
+ "modified_by": "Administrator", 
+ "module": "HR", 
+ "name": "Employee Tax Exemption Sub Category", 
+ "name_case": "", 
+ "owner": "Administrator", 
+ "permissions": [
+  {
+   "amend": 0, 
+   "apply_user_permissions": 0, 
+   "cancel": 0, 
+   "create": 1, 
+   "delete": 1, 
+   "email": 1, 
+   "export": 1, 
+   "if_owner": 0, 
+   "import": 0, 
+   "permlevel": 0, 
+   "print": 1, 
+   "read": 1, 
+   "report": 1, 
+   "role": "System Manager", 
+   "set_user_permissions": 0, 
+   "share": 1, 
+   "submit": 0, 
+   "write": 1
+  }, 
+  {
+   "amend": 0, 
+   "apply_user_permissions": 0, 
+   "cancel": 0, 
+   "create": 1, 
+   "delete": 1, 
+   "email": 1, 
+   "export": 1, 
+   "if_owner": 0, 
+   "import": 0, 
+   "permlevel": 0, 
+   "print": 1, 
+   "read": 1, 
+   "report": 1, 
+   "role": "HR Manager", 
+   "set_user_permissions": 0, 
+   "share": 1, 
+   "submit": 0, 
+   "write": 1
+  }, 
+  {
+   "amend": 0, 
+   "apply_user_permissions": 0, 
+   "cancel": 0, 
+   "create": 1, 
+   "delete": 1, 
+   "email": 1, 
+   "export": 1, 
+   "if_owner": 0, 
+   "import": 0, 
+   "permlevel": 0, 
+   "print": 1, 
+   "read": 1, 
+   "report": 1, 
+   "role": "HR User", 
+   "set_user_permissions": 0, 
+   "share": 1, 
+   "submit": 0, 
+   "write": 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
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py
new file mode 100644
index 0000000..cd58136
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.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 EmployeeTaxExemptionSubCategory(Document):
+	pass
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
new file mode 100644
index 0000000..8a1a6d1
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+// rename this file from _test_[name] to test_[name] to activate
+// and remove above this line
+
+QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) {
+	let done = assert.async();
+
+	// number of asserts
+	assert.expect(1);
+
+	frappe.run_serially([
+		// insert a new Employee Tax Exemption Sub Category
+		() => frappe.tests.make('Employee Tax Exemption Sub Category', [
+			// values to be set
+			{key: 'value'}
+		]),
+		() => {
+			assert.equal(cur_frm.doc.key, 'value');
+		},
+		() => done()
+	]);
+
+});
diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py
new file mode 100644
index 0000000..5d705567
--- /dev/null
+++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+class TestEmployeeTaxExemptionSubCategory(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 213d46e..8ab0537 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -128,6 +128,30 @@
 	else:
 		return False
 
+def update_employee(employee, details, cancel=False):
+	for item in details:
+		fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype
+		new_data = item.new if not cancel else item.current
+		if fieldtype == "Date" and new_data:
+			new_data = getdate(new_data)
+		elif fieldtype =="Datetime" and new_data:
+			new_data = get_datetime(new_data)
+		setattr(employee, item.fieldname, new_data)
+	return employee
+
+def validate_tax_declaration(declarations):
+	subcategories = []
+	for declaration in declarations:
+		if declaration.exemption_sub_category in  subcategories:
+			frappe.throw(_("More than one selection for {0} not \
+			allowed").format(declaration.exemption_sub_category), frappe.ValidationError)
+		subcategories.append(declaration.exemption_sub_category)
+		max_amount = frappe.db.get_value("Employee Tax Exemption Sub Category", \
+		declaration.exemption_sub_category, "max_amount")
+		if declaration.amount > max_amount:
+			frappe.throw(_("Max exemption amount for {0} is {1}").format(\
+			declaration.exemption_sub_category, max_amount), frappe.ValidationError)
+
 def get_leave_period(from_date, to_date, company):
 	leave_period = frappe.db.sql("""
 		select name, from_date, to_date
@@ -144,3 +168,4 @@
 
 	if leave_period:
 		return leave_period
+