[Leave Application] [fixes] Validate Leave Approver based on a list specified in Employee

This commit is contained in:
Anand Doshi 2013-04-12 20:19:53 +05:30
parent b094ae7f23
commit 60666a278f
13 changed files with 424 additions and 136 deletions

View File

@ -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;
},
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});

View File

@ -27,7 +27,7 @@ class DocType:
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 @@ class DocType:
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 @@ class DocType:
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 @@ class DocType:
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

View File

@ -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"
},
@ -322,15 +322,6 @@
"options": "Grade",
"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",
@ -342,6 +333,14 @@
"oldfieldtype": "Data",
"reqd": 0
},
{
"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",
@ -405,6 +404,29 @@
"oldfieldname": "gratuity_lic_id",
"oldfieldtype": "Data"
},
{
"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",
@ -429,14 +451,6 @@
"fieldtype": "Data",
"label": "Personal Email"
},
{
"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",
@ -767,4 +781,4 @@
"role": "HR Manager",
"write": 1
}
]
]

View File

@ -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

View File

@ -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"
}
]

View File

@ -29,7 +29,7 @@ cur_frm.cscript.onload = function(doc,cdt,cdn){
}
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);
}

View File

@ -52,12 +52,3 @@ class DocType:
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

View File

@ -26,7 +26,7 @@ cur_frm.cscript.onload = function(doc, dt, dn) {
}
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) {

View File

@ -18,11 +18,13 @@ from __future__ import unicode_literals
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 @@ class DocType(DocListController):
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 @@ class DocType(DocListController):
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 @@ def get_leave_balance(employee, leave_type, fiscal_year):
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

View File

@ -4,6 +4,23 @@ import unittest
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 @@ class TestLeaveApplication(unittest.TestCase):
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 @@ class TestLeaveApplication(unittest.TestCase):
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 @@ class TestLeaveApplication(unittest.TestCase):
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 @@ class TestLeaveApplication(unittest.TestCase):
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 = [

38
hr/utils.py Normal file
View File

@ -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

View File

@ -136,10 +136,7 @@ erpnext.startup.set_periodic_updates = function() {
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);
}
}