Merge branch 'master' of github.com:webnotes/erpnext
diff --git a/accounts/page/general_ledger/general_ledger.js b/accounts/page/general_ledger/general_ledger.js
index 4a39d74..21be3a0 100644
--- a/accounts/page/general_ledger/general_ledger.js
+++ b/accounts/page/general_ledger/general_ledger.js
@@ -307,10 +307,10 @@
 
 	make_account_by_name: function() {
 		this.account_by_name = this.make_name_map(wn.report_dump.data["Account"]);
-		this.make_voucher_acconuts_map();
+		this.make_voucher_accounts_map();
 	},
 	
-	make_voucher_acconuts_map: function() {
+	make_voucher_accounts_map: function() {
 		this.voucher_accounts = {};
 		var data = wn.report_dump.data["GL Entry"];
 		for(var i=0, j=data.length; i<j; i++) {
diff --git a/home/page/latest_updates/latest_updates.js b/home/page/latest_updates/latest_updates.js
index c0bad94..949849d 100644
--- a/home/page/latest_updates/latest_updates.js
+++ b/home/page/latest_updates/latest_updates.js
@@ -1,4 +1,5 @@
 erpnext.updates = [
+	["12th April", ["Employee: List of Leave Approvers who can approve the Employee's Leave Applications"]],
 	["27th March", ["Rename multiple items together. Go to Setup > Rename Tool"]],
 	["26th March", ["Added project to Stock Ledger and Balance",
 		"Added Default Cash Account in Company."]],
diff --git a/hr/doctype/employee/employee.js b/hr/doctype/employee/employee.js
index 239b3b7..5a2dbab 100644
--- a/hr/doctype/employee/employee.js
+++ b/hr/doctype/employee/employee.js
@@ -14,62 +14,95 @@
 // You should have received a copy of the GNU General Public License
 // along with this program.	If not, see <http://www.gnu.org/licenses/>.
 
-cur_frm.cscript.onload = function(doc) {
-	// bc
-	var india_specific = ["esic_card_no", "gratuity_lic_id", "pan_number", "pf_number"]
-	if(wn.control_panel.country!="India") {
-		hide_field(india_specific);
-	}
-}
-
-cur_frm.cscript.refresh = function(doc) {
-	if(!doc.__islocal) {
-		hide_field("naming_series");
-		cur_frm.add_custom_button('Make Salary Structure', 
-			cur_frm.cscript['Make Salary Structure']);
-	}
-}
-
-cur_frm.cscript.date_of_birth = function(doc, dt, dn) {
-	get_server_fields('get_retirement_date','','',doc,dt,dn,1);
-}
-
-cur_frm.cscript.salutation = function(doc,dt,dn) {
-	if(doc.salutation){
-		if(doc.salutation=='Mr')
-			doc.gender='Male';
-		else if(doc.salutation=='Ms')
-			doc.gender='Female';
-		refresh_field('gender');
-	}
-}
-
-cur_frm.cscript['Make Salary Structure']=function(){
-	$c_obj(make_doclist (cur_frm.doc.doctype, cur_frm.doc.name), 'check_sal_structure',
-	 	cur_frm.doc.name, function(r, rt) {
-			if(r.message)
-				msgprint("You have already created Active salary structure.\n \
-					If you want to create new one, please ensure that no active salary structure \
-					 exist.\nTo inactive salary structure select 'Is Active' as 'No'.");
-			else
-				cur_frm.cscript.make_salary_structure(cur_frm.doc); 
+wn.provide("erpnext.hr");
+erpnext.hr.EmployeeController = wn.ui.form.Controller.extend({
+	setup: function() {
+		this.setup_leave_approver_select();
+		this.frm.fields_dict.user_id.get_query = erpnext.utils.profile_query;
+		this.frm.fields_dict.reports_to.get_query = erpnext.utils.employee_query;
+	},
+	
+	onload: function() {
+		this.frm.toggle_display(["esic_card_no", "gratuity_lic_id", "pan_number", "pf_number"],
+			wn.control_panel.country==="India");
+	},
+	
+	refresh: function() {
+		var me = this;
+		erpnext.hide_naming_series();
+		if(!this.frm.doc.__islocal) {
+			cur_frm.add_custom_button('View Active Salary Structure', function() {
+				me.view_active_salary_structure(this); });
+			
+			cur_frm.add_custom_button('Make Salary Structure', function() {
+				me.make_salary_structure(this); });
+			
 		}
-	);
-}
-
-cur_frm.cscript.make_salary_structure = function(doc, dt, dn, det){
-	var st = wn.model.make_new_doc_and_get_name('Salary Structure');
-	st = locals['Salary Structure'][st];
-	st.employee = doc.name;
-	st.employee_name = doc.employee_name;
-	st.branch=doc.branch;
-	st.designation=doc.designation;
-	st.department=doc.department;
-	st.fiscal_year = doc.fiscal_year
-	st.grade=doc.grade;
-	loaddoc('Salary Structure', st.name);
-}
-
-cur_frm.fields_dict.user_id.get_query = erpnext.utils.profile_query;
-
-cur_frm.fields_dict.reports_to.get_query = erpnext.utils.employee_query;
\ No newline at end of file
+	},
+	
+	setup_leave_approver_select: function() {
+		var me = this;
+		this.frm.call({
+			method:"hr.utils.get_leave_approver_list",
+			callback: function(r) {
+				me.frm.fields_dict.employee_leave_approvers.grid.get_field("leave_approver").df.options =
+					$.map(r.message, function(profile) { 
+						return {value: profile, label: wn.user_info(profile).fullname}; 
+					});
+			}
+		});
+	},
+	
+	date_of_birth: function() {
+		cur_frm.call({
+			method: "get_retirement_date",
+			args: {date_of_birth: this.frm.doc.date_of_birth}
+		});
+	},
+	
+	salutation: function() {
+		if(this.frm.doc.salutation) {
+			this.frm.set_value("gender", {
+				"Mr": "Male",
+				"Ms": "Female"
+			}[this.frm.doc.salutation]);
+		}
+	},
+	
+	make_salary_structure: function(btn) {
+		var me = this;
+		this.validate_salary_structure(btn, function(r) {
+			if(r.message) {
+				msgprint(wn._("Employee") + ' "' + me.frm.doc.name + '": ' 
+					+ wn._("An active Salary Structure already exists. \
+						If you want to create new one, please ensure that no active Salary Structure \
+					 	exists for this Employee. Go to the active Salary Structure and set \
+						\"Is Active\" = \"No\""));
+			} else if(!r.exc) {
+				wn.model.map({
+					source: wn.model.get_doclist(me.frm.doc.doctype, me.frm.doc.name),
+					target: "Salary Structure"
+				});
+			}
+		});
+	},
+	
+	validate_salary_structure: function(btn, callback) {
+		var me = this;
+		this.frm.call({
+			btn: btn,
+			method: "webnotes.client.get_value",
+			args: {
+				doctype: "Salary Structure",
+				fieldname: "name",
+				filters: {
+					employee: me.frm.doc.name,
+					is_active: "Yes",
+					docstatus: ["!=", 2]
+				},
+			},
+			callback: callback
+		});
+	},
+});
+cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm});
\ No newline at end of file
diff --git a/hr/doctype/employee/employee.py b/hr/doctype/employee/employee.py
index 16ecb6d..59c8380 100644
--- a/hr/doctype/employee/employee.py
+++ b/hr/doctype/employee/employee.py
@@ -27,7 +27,7 @@
 	def __init__(self,doc,doclist=[]):
 		self.doc = doc
 		self.doclist = doclist
-
+		
 	def autoname(self):
 		ret = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'emp_created_by'")
 		if not ret:
@@ -49,30 +49,31 @@
 		self.validate_email()
 		self.validate_name()
 		self.validate_status()
-				
-	def get_retirement_date(self):		
-		import datetime
-		ret = {}
-		if self.doc.date_of_birth:
-			dt = getdate(self.doc.date_of_birth) + datetime.timedelta(21915)
-			ret = {'date_of_retirement': dt.strftime('%Y-%m-%d')}
-		return ret
-
-	def check_sal_structure(self, nm):
-		ret_sal_struct=sql("select name from `tabSalary Structure` where employee='%s' and is_active = 'Yes' and docstatus!= 2"%nm)
-		return ret_sal_struct and ret_sal_struct[0][0] or ''
-
+		self.validate_employee_leave_approver()
+		
 	def on_update(self):
 		if self.doc.user_id:
 			self.update_user_default()
 			self.update_profile()
-	
+				
 	def update_user_default(self):
 		webnotes.conn.set_default("employee", self.doc.name, self.doc.user_id)
 		webnotes.conn.set_default("employee_name", self.doc.employee_name, self.doc.user_id)
 		webnotes.conn.set_default("company", self.doc.company, self.doc.user_id)
-		if self.doc.reports_to:
-			webnotes.conn.set_default("leave_approver", webnotes.conn.get_value("Employee", self.doc.reports_to, "user_id"), self.doc.user_id)
+		self.set_default_leave_approver()
+	
+	def set_default_leave_approver(self):
+		employee_leave_approvers = self.doclist.get({"parentfield": "employee_leave_approvers"})
+
+		if len(employee_leave_approvers):
+			webnotes.conn.set_default("leave_approver", employee_leave_approvers[0].leave_approver,
+				self.doc.user_id)
+		
+		elif self.doc.reports_to:
+			from webnotes.profile import Profile
+			reports_to_user = webnotes.conn.get_value("Employee", self.doc.reports_to, "user_id")
+			if "Leave Approver" in Profile(reports_to_user).get_roles():
+				webnotes.conn.set_default("leave_approver", reports_to_user, self.doc.user_id)
 
 	def update_profile(self):
 		# add employee role if missing
@@ -116,7 +117,6 @@
 		profile_wrapper.save()
 		
 	def validate_date(self):
-		import datetime
 		if self.doc.date_of_birth and self.doc.date_of_joining and getdate(self.doc.date_of_birth) >= getdate(self.doc.date_of_joining):
 			msgprint('Date of Joining must be greater than Date of Birth')
 			raise Exception
@@ -167,3 +167,21 @@
 		if self.doc.status == 'Left' and not self.doc.relieving_date:
 			msgprint("Please enter relieving date.")
 			raise Exception
+			
+	def validate_employee_leave_approver(self):
+		from webnotes.profile import Profile
+		from hr.doctype.leave_application.leave_application import InvalidLeaveApproverError
+		
+		for l in self.doclist.get({"parentfield": "employee_leave_approvers"}):
+			if "Leave Approver" not in Profile(l.leave_approver).get_roles():
+				msgprint(_("Invalid Leave Approver") + ": \"" + l.leave_approver + "\"",
+					raise_exception=InvalidLeaveApproverError)
+
+@webnotes.whitelist()
+def get_retirement_date(date_of_birth=None):
+	import datetime
+	ret = {}
+	if date_of_birth:
+		dt = getdate(date_of_birth) + datetime.timedelta(21915)
+		ret = {'date_of_retirement': dt.strftime('%Y-%m-%d')}
+	return ret
diff --git a/hr/doctype/employee/employee.txt b/hr/doctype/employee/employee.txt
index 7b1f730..997a1ef 100644
--- a/hr/doctype/employee/employee.txt
+++ b/hr/doctype/employee/employee.txt
@@ -1,8 +1,8 @@
 [
  {
-  "creation": "2013-01-23 19:57:17", 
+  "creation": "2013-03-07 09:04:18", 
   "docstatus": 0, 
-  "modified": "2013-02-08 13:07:25", 
+  "modified": "2013-04-12 07:16:42", 
   "modified_by": "Administrator", 
   "owner": "Administrator"
  }, 
@@ -323,15 +323,6 @@
   "reqd": 0
  }, 
  {
-  "doctype": "DocField", 
-  "fieldname": "reports_to", 
-  "fieldtype": "Link", 
-  "label": "Reports to", 
-  "oldfieldname": "reports_to", 
-  "oldfieldtype": "Link", 
-  "options": "Employee"
- }, 
- {
   "description": "Provide email id registered in company", 
   "doctype": "DocField", 
   "fieldname": "company_email", 
@@ -344,6 +335,14 @@
  }, 
  {
   "doctype": "DocField", 
+  "fieldname": "notice_number_of_days", 
+  "fieldtype": "Int", 
+  "label": "Notice - Number of Days", 
+  "oldfieldname": "notice_number_of_days", 
+  "oldfieldtype": "Int"
+ }, 
+ {
+  "doctype": "DocField", 
   "fieldname": "salary_information", 
   "fieldtype": "Column Break", 
   "label": "Salary Information", 
@@ -407,6 +406,29 @@
  }, 
  {
   "doctype": "DocField", 
+  "fieldname": "organization_profile", 
+  "fieldtype": "Section Break", 
+  "label": "Organization Profile"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "reports_to", 
+  "fieldtype": "Link", 
+  "label": "Reports to", 
+  "oldfieldname": "reports_to", 
+  "oldfieldtype": "Link", 
+  "options": "Employee"
+ }, 
+ {
+  "description": "The first Leave Approver in the list will be set as the default Leave Approver", 
+  "doctype": "DocField", 
+  "fieldname": "employee_leave_approvers", 
+  "fieldtype": "Table", 
+  "label": "Leave Approvers", 
+  "options": "Employee Leave Approver"
+ }, 
+ {
+  "doctype": "DocField", 
   "fieldname": "contact_details", 
   "fieldtype": "Section Break", 
   "label": "Contact Details"
@@ -431,14 +453,6 @@
  }, 
  {
   "doctype": "DocField", 
-  "fieldname": "notice_number_of_days", 
-  "fieldtype": "Int", 
-  "label": "Notice - Number of Days", 
-  "oldfieldname": "notice_number_of_days", 
-  "oldfieldtype": "Int"
- }, 
- {
-  "doctype": "DocField", 
   "fieldname": "emergency_contact_details", 
   "fieldtype": "HTML", 
   "label": "Emergency Contact Details", 
@@ -767,4 +781,4 @@
   "role": "HR Manager", 
   "write": 1
  }
-]
+]
\ No newline at end of file
diff --git a/hr/doctype/employee_leave_approver/__init__.py b/hr/doctype/employee_leave_approver/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/hr/doctype/employee_leave_approver/__init__.py
diff --git a/hr/doctype/employee_leave_approver/employee_leave_approver.py b/hr/doctype/employee_leave_approver/employee_leave_approver.py
new file mode 100644
index 0000000..928aa9f
--- /dev/null
+++ b/hr/doctype/employee_leave_approver/employee_leave_approver.py
@@ -0,0 +1,8 @@
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import webnotes
+
+class DocType:
+	def __init__(self, d, dl):
+		self.doc, self.doclist = d, dl
\ No newline at end of file
diff --git a/hr/doctype/employee_leave_approver/employee_leave_approver.txt b/hr/doctype/employee_leave_approver/employee_leave_approver.txt
new file mode 100644
index 0000000..31e3e09
--- /dev/null
+++ b/hr/doctype/employee_leave_approver/employee_leave_approver.txt
@@ -0,0 +1,39 @@
+[
+ {
+  "creation": "2013-04-12 06:56:15", 
+  "docstatus": 0, 
+  "modified": "2013-04-12 07:53:33", 
+  "modified_by": "Administrator", 
+  "owner": "Administrator"
+ }, 
+ {
+  "allow_import": 0, 
+  "autoname": "LAPPR-/.#####", 
+  "description": "Users who can approve a specific employee's leave applications", 
+  "doctype": "DocType", 
+  "istable": 1, 
+  "module": "HR", 
+  "name": "__common__"
+ }, 
+ {
+  "doctype": "DocField", 
+  "fieldname": "leave_approver", 
+  "fieldtype": "Select", 
+  "label": "Leave Approver", 
+  "name": "__common__", 
+  "parent": "Employee Leave Approver", 
+  "parentfield": "fields", 
+  "parenttype": "DocType", 
+  "permlevel": 0, 
+  "print_hide": 1, 
+  "reqd": 1, 
+  "width": "200"
+ }, 
+ {
+  "doctype": "DocType", 
+  "name": "Employee Leave Approver"
+ }, 
+ {
+  "doctype": "DocField"
+ }
+]
\ No newline at end of file
diff --git a/hr/doctype/expense_claim/expense_claim.js b/hr/doctype/expense_claim/expense_claim.js
index 72fe15c..5b136d0 100644
--- a/hr/doctype/expense_claim/expense_claim.js
+++ b/hr/doctype/expense_claim/expense_claim.js
@@ -29,7 +29,7 @@
 	}
 
 	cur_frm.call({
-		method:"get_approver_list",
+		method:"hr.utils.get_expense_approver_list",
 		callback: function(r) {
 			cur_frm.set_df_property("exp_approver", "options", r.message);
 		}
diff --git a/hr/doctype/expense_claim/expense_claim.py b/hr/doctype/expense_claim/expense_claim.py
index 0aa9ed8..0564d1d 100644
--- a/hr/doctype/expense_claim/expense_claim.py
+++ b/hr/doctype/expense_claim/expense_claim.py
@@ -52,12 +52,3 @@
 		if not getlist(self.doclist, 'expense_voucher_details'):
 			msgprint("Please add expense voucher details")
 			raise Exception
-		
-@webnotes.whitelist()
-def get_approver_list():
-	roles = [r[0] for r in webnotes.conn.sql("""select distinct parent from `tabUserRole`
-		where role='Expense Approver'""")]
-	if not roles:
-		webnotes.msgprint("No Expense Approvers. Please assign 'Expense Approver' \
-			Role to atleast one user.")
-	return roles
diff --git a/hr/doctype/leave_application/leave_application.js b/hr/doctype/leave_application/leave_application.js
index 0252818..7f8948a 100755
--- a/hr/doctype/leave_application/leave_application.js
+++ b/hr/doctype/leave_application/leave_application.js
@@ -26,7 +26,7 @@
 	}
 	cur_frm.set_df_property("leave_approver", "options", "");
 	cur_frm.call({
-		method:"get_approver_list",
+		method:"hr.utils.get_leave_approver_list",
 		callback: function(r) {
 			cur_frm.set_df_property("leave_approver", "options", $.map(r.message, 
 				function(profile) { 
diff --git a/hr/doctype/leave_application/leave_application.py b/hr/doctype/leave_application/leave_application.py
index d34abd8..b9f9e5b 100755
--- a/hr/doctype/leave_application/leave_application.py
+++ b/hr/doctype/leave_application/leave_application.py
@@ -18,11 +18,13 @@
 import webnotes
 from webnotes import _
 
-from webnotes.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_url_to_form
+from webnotes.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_url_to_form, \
+	comma_or, get_fullname
 from webnotes import msgprint
 
-class LeaveDayBlockedError(Exception): pass
-class OverlapError(Exception): pass
+class LeaveDayBlockedError(webnotes.ValidationError): pass
+class OverlapError(webnotes.ValidationError): pass
+class InvalidLeaveApproverError(webnotes.ValidationError): pass
 	
 from webnotes.model.controller import DocListController
 class DocType(DocListController):
@@ -39,6 +41,7 @@
 		self.validate_max_days()
 		self.show_block_day_warning()
 		self.validate_block_days()
+		self.validate_leave_approver()
 		
 	def on_update(self):
 		if (not self.previous_doc and self.doc.leave_approver) or (self.previous_doc and \
@@ -156,6 +159,21 @@
 			msgprint("Sorry ! You cannot apply for %s for more than %s days" % (self.doc.leave_type, max_days))
 			raise Exception
 			
+	def validate_leave_approver(self):
+		employee = webnotes.bean("Employee", self.doc.employee)
+		leave_approvers = [l.leave_approver for l in 
+			employee.doclist.get({"parentfield": "employee_leave_approvers"})]
+
+		if len(leave_approvers) and self.doc.leave_approver not in leave_approvers:
+			msgprint(("[" + _("For Employee") + ' "' + self.doc.employee + '"] ' 
+				+ _("Leave Approver can be one of") + ": "
+				+ comma_or(leave_approvers)), raise_exception=InvalidLeaveApproverError)
+		
+		elif self.doc.leave_approver and not webnotes.conn.sql("""select name from `tabUserRole` 
+			where parent=%s and role='Leave Approver'""", self.doc.leave_approver):
+				msgprint(get_fullname(self.doc.leave_approver) + ": " \
+					+ _("does not have role 'Leave Approver'"), raise_exception=InvalidLeaveApproverError)
+			
 	def notify_employee(self, status):
 		employee = webnotes.doc("Employee", self.doc.employee)
 		if not employee.user_id:
@@ -221,15 +239,6 @@
 	ret = {'leave_balance': leave_all - leave_app}
 	return ret
 
-@webnotes.whitelist()
-def get_approver_list():
-	roles = [r[0] for r in webnotes.conn.sql("""select distinct parent from `tabUserRole`
-		where role='Leave Approver'""")]
-	if not roles:
-		webnotes.msgprint("No Leave Approvers. Please assign 'Leave Approver' Role to atleast one user.")
-		
-	return roles
-
 def is_lwp(leave_type):
 	lwp = webnotes.conn.sql("select is_lwp from `tabLeave Type` where name = %s", leave_type)
 	return lwp and cint(lwp[0][0]) or 0
diff --git a/hr/doctype/leave_application/test_leave_application.py b/hr/doctype/leave_application/test_leave_application.py
index 672e668..338225c 100644
--- a/hr/doctype/leave_application/test_leave_application.py
+++ b/hr/doctype/leave_application/test_leave_application.py
@@ -4,6 +4,23 @@
 from hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError
 
 class TestLeaveApplication(unittest.TestCase):
+	def _clear_roles(self):
+		webnotes.conn.sql("""delete from `tabUserRole` where parent in 
+			("test@example.com", "test1@example.com", "test2@example.com")""")
+			
+	def _clear_applications(self):
+		webnotes.conn.sql("""delete from `tabLeave Application`""")
+		
+	def _add_employee_leave_approver(self, employee, leave_approver):
+		webnotes.session.user = "Administrator"
+		employee = webnotes.bean("Employee", employee)
+		employee.doclist.append({
+			"doctype": "Employee Leave Approver",
+			"parentfield": "employee_leave_approvers",
+			"leave_approver": leave_approver
+		})
+		employee.save()
+	
 	def get_application(self, doclist):
 		application = webnotes.bean(copy=doclist)
 		application.doc.from_date = "2013-01-01"
@@ -11,8 +28,14 @@
 		return application
 
 	def test_block_list(self):
-		import webnotes
-		webnotes.conn.set_value("Department", "_Test Department", "leave_block_list", "_Test Leave Block List")
+		webnotes.session.user = "Administrator"
+		self._clear_roles()
+		
+		from webnotes.profile import add_role
+		add_role("test1@example.com", "HR User")
+			
+		webnotes.conn.set_value("Department", "_Test Department", 
+			"leave_block_list", "_Test Leave Block List")
 		
 		application = self.get_application(test_records[1])
 		application.insert()
@@ -20,9 +43,6 @@
 		self.assertRaises(LeaveDayBlockedError, application.submit)
 		
 		webnotes.session.user = "test1@example.com"
-		
-		from webnotes.profile import add_role
-		add_role("test1@example.com", "HR User")
 
 		# clear other applications
 		webnotes.conn.sql("delete from `tabLeave Application`")
@@ -31,11 +51,31 @@
 		self.assertTrue(application.insert())
 		
 	def test_overlap(self):
+		webnotes.session.user = "Administrator"
+		self._clear_roles()
+		self._clear_applications()
+		
+		from webnotes.profile import add_role
+		add_role("test@example.com", "Employee")
+		add_role("test2@example.com", "Leave Approver")
+		
+		webnotes.session.user = "test@example.com"
 		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test2@example.com"
+		application.insert()
+		
+		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test2@example.com"
 		self.assertRaises(OverlapError, application.insert)
 		
 	def test_global_block_list(self):
-		
+		webnotes.session.user = "Administrator"
+		self._clear_roles()
+
+		from webnotes.profile import add_role
+		add_role("test1@example.com", "Employee")
+		add_role("test@example.com", "Leave Approver")
+				
 		application = self.get_application(test_records[3])
 		application.doc.leave_approver = "test@example.com"
 		
@@ -44,19 +84,120 @@
 		webnotes.conn.set_value("Employee", "_T-Employee-0002", "department", 
 			"_Test Department")
 		
-		webnotes.session.user = "test2@example.com"
-		from webnotes.profile import add_role
-		add_role("test2@example.com", "Employee")
-
+		webnotes.session.user = "test1@example.com"
 		application.insert()
 		
 		webnotes.session.user = "test@example.com"
-		from webnotes.profile import add_role
-		add_role("test@example.com", "Leave Approver")
-		
 		application.doc.status = "Approved"
 		self.assertRaises(LeaveDayBlockedError, application.submit)
 		
+		webnotes.conn.set_value("Leave Block List", "_Test Leave Block List", 
+			"applies_to_all_departments", 0)
+		
+	def test_leave_approval(self):
+		webnotes.session.user = "Administrator"
+		self._clear_roles()
+		
+		from webnotes.profile import add_role
+		add_role("test@example.com", "Employee")
+		add_role("test1@example.com", "Leave Approver")
+		add_role("test2@example.com", "Leave Approver")
+		
+		self._test_leave_approval_basic_case_1()
+		self._test_leave_approval_basic_case_2()
+		self._test_leave_approval_invalid_leave_approver_insert()
+		self._test_leave_approval_invalid_leave_approver_submit()
+		self._test_leave_approval_valid_leave_approver_insert()
+		
+	def _test_leave_approval_basic_case_1(self):
+		self._clear_applications()
+		
+		# create leave application as Employee
+		webnotes.session.user = "test@example.com"
+		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test1@example.com"
+		application.insert()
+		
+		# submit leave application by Leave Approver
+		webnotes.session.user = "test1@example.com"
+		application.doc.status = "Approved"
+		application.submit()
+		self.assertEqual(webnotes.conn.get_value("Leave Application", application.doc.name,
+			"docstatus"), 1)
+		
+	def _test_leave_approval_basic_case_2(self):
+		self._clear_applications()
+		
+		# create leave application by any leave approver, 
+		# when no leave approver specified in employee's leave approvers list
+		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test1@example.com"
+		application.insert()
+		application.doc.status = "Approved"
+		application.submit()
+		self.assertEqual(webnotes.conn.get_value("Leave Application", application.doc.name,
+			"docstatus"), 1)
+		
+	def _test_leave_approval_invalid_leave_approver_insert(self):
+		from hr.doctype.leave_application.leave_application import InvalidLeaveApproverError
+		
+		self._clear_applications()
+		
+		# add a different leave approver in the employee's list
+		# should raise exception if not a valid leave approver
+		self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
+		
+		# TODO - add test2@example.com leave approver in employee's leave approvers list
+		application = self.get_application(test_records[1])
+		webnotes.session.user = "test@example.com"
+		
+		application.doc.leave_approver = "test1@example.com"
+		self.assertRaises(InvalidLeaveApproverError, application.insert)
+		
+		webnotes.conn.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
+			"_T-Employee-0001")
+		
+	def _test_leave_approval_invalid_leave_approver_submit(self):
+		self._clear_applications()
+		self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
+		
+		# create leave application as employee
+		# but submit as invalid leave approver - should raise exception
+		webnotes.session.user = "test@example.com"
+		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test2@example.com"
+		application.insert()
+		webnotes.session.user = "test1@example.com"
+		application.doc.status = "Approved"
+		
+		from webnotes.model.bean import BeanPermissionError
+		self.assertRaises(BeanPermissionError, application.submit)
+		
+		webnotes.conn.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
+			"_T-Employee-0001")
+		
+	def _test_leave_approval_valid_leave_approver_insert(self):
+		self._clear_applications()
+		self._add_employee_leave_approver("_T-Employee-0001", "test2@example.com")
+		
+		original_department = webnotes.conn.get_value("Employee", "_T-Employee-0001", "department")
+		webnotes.conn.set_value("Employee", "_T-Employee-0001", "department", None)
+		
+		# change to valid leave approver and try to create and submit leave application
+		webnotes.session.user = "test2@example.com"
+		application = self.get_application(test_records[1])
+		application.doc.leave_approver = "test2@example.com"
+		application.insert()
+		application.doc.status = "Approved"
+		application.submit()
+		self.assertEqual(webnotes.conn.get_value("Leave Application", application.doc.name,
+			"docstatus"), 1)
+			
+		webnotes.conn.sql("""delete from `tabEmployee Leave Approver` where parent=%s""",
+			"_T-Employee-0001")
+		
+		webnotes.conn.set_value("Employee", "_T-Employee-0001", "department", original_department)
+		
 test_dependencies = ["Leave Block List"]		
 
 test_records = [
diff --git a/hr/utils.py b/hr/utils.py
new file mode 100644
index 0000000..0d23a16
--- /dev/null
+++ b/hr/utils.py
@@ -0,0 +1,38 @@
+# ERPNext - web based ERP (http://erpnext.com)
+# Copyright (C) 2012 Web Notes Technologies Pvt Ltd
+# 
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import unicode_literals
+import webnotes
+from webnotes import _
+
+@webnotes.whitelist()
+def get_leave_approver_list():
+	roles = [r[0] for r in webnotes.conn.sql("""select distinct parent from `tabUserRole`
+		where role='Leave Approver'""")]
+	if not roles:
+		webnotes.msgprint(_("No Leave Approvers. Please assign 'Leave Approver' Role to atleast one user."))
+		
+	return roles
+
+
+@webnotes.whitelist()
+def get_expense_approver_list():
+	roles = [r[0] for r in webnotes.conn.sql("""select distinct parent from `tabUserRole`
+		where role='Expense Approver'""")]
+	if not roles:
+		webnotes.msgprint("No Expense Approvers. Please assign 'Expense Approver' \
+			Role to atleast one user.")
+	return roles
diff --git a/public/js/startup.js b/public/js/startup.js
index 0a6580b..76f2c26 100644
--- a/public/js/startup.js
+++ b/public/js/startup.js
@@ -136,10 +136,7 @@
 
 erpnext.hide_naming_series = function() {
 	if(cur_frm.fields_dict.naming_series) {
-		hide_field('naming_series');
-		if(cur_frm.doc.__islocal) {
-			unhide_field('naming_series');
-		}
+		cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal?true:false);
 	}
 }