From 6227527cdbcc28e1e9644d7b094194a53057a7b3 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 14 Feb 2013 20:42:33 +0530 Subject: [PATCH] added leave applications and block lists to calendar --- hr/doctype/department/test_department.py | 3 +- hr/doctype/employee/test_employee.py | 9 +- .../leave_application/leave_application.py | 49 ++---- .../test_leave_application.py | 1 + .../leave_block_list/leave_block_list.py | 45 ++++++ .../leave_block_list/test_leave_block_list.py | 28 ++++ .../leave_block_list_allow.txt | 5 +- .../leave_block_list_date.txt | 5 +- utilities/page/calendar/calendar.js | 153 ++++++++++++------ utilities/page/calendar/calendar.py | 80 +++++++-- 10 files changed, 267 insertions(+), 111 deletions(-) diff --git a/hr/doctype/department/test_department.py b/hr/doctype/department/test_department.py index c4664a51126..40125831f0e 100644 --- a/hr/doctype/department/test_department.py +++ b/hr/doctype/department/test_department.py @@ -1,5 +1,4 @@ test_records = [ [{"doctype":"Department", "department_name":"_Test Department"}], - [{"doctype":"Department", "department_name":"_Test Department with Block List", - "leave_block_list": "_Test Leave Block List"}], + [{"doctype":"Department", "department_name":"_Test Department 1"}] ] diff --git a/hr/doctype/employee/test_employee.py b/hr/doctype/employee/test_employee.py index 01c2087ec0b..ed7ce94635c 100644 --- a/hr/doctype/employee/test_employee.py +++ b/hr/doctype/employee/test_employee.py @@ -7,7 +7,8 @@ test_records = [[{ "gender": "Female", "status": "Active", "company": "_Test Company", - "user_id": "test@example.com" + "user_id": "test@example.com", + "department": "_Test Department" }], [{ "doctype":"Employee", @@ -18,7 +19,8 @@ test_records = [[{ "gender": "Male", "status": "Active", "company": "_Test Company", - "user_id": "test1@example.com" + "user_id": "test1@example.com", + "department": "_Test Department 1" }], [{ "doctype":"Employee", @@ -29,6 +31,7 @@ test_records = [[{ "gender": "Male", "status": "Active", "company": "_Test Company", - "user_id": "test2@example.com" + "user_id": "test2@example.com", + "department": "_Test Department 1" }] ] \ No newline at end of file diff --git a/hr/doctype/leave_application/leave_application.py b/hr/doctype/leave_application/leave_application.py index 2e26eb34c94..17f8526f2d8 100755 --- a/hr/doctype/leave_application/leave_application.py +++ b/hr/doctype/leave_application/leave_application.py @@ -46,46 +46,19 @@ class DocType: raise_exception=True) def validate_block_days(self): - for block_list in self.get_applicable_block_lists(): - self.check_block_dates(block_list) + from hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates - def get_applicable_block_lists(self): - block_lists = [] - def add_block_list(block_list): - if block_list: - if not self.is_user_in_allow_list(block_list): - block_lists.append(block_list) - - # per department - department = webnotes.conn.get_value("Employee", self.doc.employee, "department") - if department: - block_list = webnotes.conn.get_value("Department", department, "leave_block_list") - add_block_list(block_list) - - # global - for block_list in webnotes.conn.sql_list("""select name from `tabLeave Block List` - where ifnull(applies_to_all_departments,0)=1 and company=%s""", self.doc.company): - add_block_list(block_list) + block_dates = get_applicable_block_dates(self.doc.from_date, self.doc.to_date, + self.doc.employee, self.doc.company) + + if block_dates: + webnotes.msgprint(_("Following dates are blocked for Leave") + ":") + for d in block_dates: + webnotes.msgprint(formatdate(d.block_date) + ": " + d.reason) - return block_lists - - def check_block_dates(self, block_list): - from_date = getdate(self.doc.from_date) - to_date = getdate(self.doc.to_date) - for d in webnotes.conn.sql("""select block_date, reason from - `tabLeave Block List Date` where parent=%s""", block_list, as_dict=1): - block_date = getdate(d.block_date) - if block_date > from_date and block_date < to_date: - webnotes.msgprint(_("You cannot apply for a leave on the following date because it is blocked") - + ": " + formatdate(d.block_date) + _(" Reason: ") + d.reason) - if self.doc.docstatus == 1: - # throw exception only when submitting - raise LeaveDayBlockedError - - def is_user_in_allow_list(self, block_list): - return webnotes.session.user in webnotes.conn.sql_list("""select allow_user - from `tabLeave Block List Allow` where parent=%s""", block_list) - + if self.doc.docstatus == 1: + raise LeaveDayBlockedError + def get_holidays(self): tot_hol = webnotes.conn.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2, `tabEmployee` e1 where e1.name = %s and h1.parent = h2.name and e1.holiday_list = h2.name diff --git a/hr/doctype/leave_application/test_leave_application.py b/hr/doctype/leave_application/test_leave_application.py index 19e2935e777..584549add3f 100644 --- a/hr/doctype/leave_application/test_leave_application.py +++ b/hr/doctype/leave_application/test_leave_application.py @@ -46,6 +46,7 @@ class TestLeaveApplication(unittest.TestCase): add_role("test@example.com", "Leave Approver") self.assertRaises(LeaveDayBlockedError, application.submit) + test_records = [ [{ diff --git a/hr/doctype/leave_block_list/leave_block_list.py b/hr/doctype/leave_block_list/leave_block_list.py index 16d73205bdb..81269ba27cf 100644 --- a/hr/doctype/leave_block_list/leave_block_list.py +++ b/hr/doctype/leave_block_list/leave_block_list.py @@ -19,3 +19,48 @@ class DocType: if d.block_date in dates: webnotes.msgprint(_("Date is repeated") + ":" + d.block_date, raise_exception=1) dates.append(d.block_date) + +@webnotes.whitelist() +def get_applicable_block_dates(from_date, to_date, employee=None, + company=None, all_lists=False): + block_dates = [] + for block_list in get_applicable_block_lists(employee, company, all_lists): + block_dates.extend(webnotes.conn.sql("""select block_date, reason + from `tabLeave Block List Date` where parent=%s + and block_date between %s and %s""", (block_list, from_date, to_date), + as_dict=1)) + + return block_dates + +def get_applicable_block_lists(employee=None, company=None, all_lists=False): + block_lists = [] + + if not employee: + employee = webnotes.conn.get_value("Employee", {"user_id":webnotes.session.user}) + if not employee: + return [] + + if not company: + company = webnotes.conn.get_value("Employee", employee, "company") + + def add_block_list(block_list): + if block_list: + if all_lists or not is_user_in_allow_list(block_list): + block_lists.append(block_list) + + # per department + department = webnotes.conn.get_value("Employee",employee, "department") + if department: + block_list = webnotes.conn.get_value("Department", department, "leave_block_list") + add_block_list(block_list) + + # global + for block_list in webnotes.conn.sql_list("""select name from `tabLeave Block List` + where ifnull(applies_to_all_departments,0)=1 and company=%s""", company): + add_block_list(block_list) + + return list(set(block_lists)) + +def is_user_in_allow_list(block_list): + return webnotes.session.user in webnotes.conn.sql_list("""select allow_user + from `tabLeave Block List Allow` where parent=%s""", block_list) \ No newline at end of file diff --git a/hr/doctype/leave_block_list/test_leave_block_list.py b/hr/doctype/leave_block_list/test_leave_block_list.py index 701787232be..9df5fcf3d2c 100644 --- a/hr/doctype/leave_block_list/test_leave_block_list.py +++ b/hr/doctype/leave_block_list/test_leave_block_list.py @@ -1,3 +1,31 @@ +import webnotes +import unittest + +from hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates + +class TestLeaveBlockList(unittest.TestCase): + def test_get_applicable_block_dates(self): + webnotes.session.user = "test@example.com" + webnotes.conn.set_value("Department", "_Test Department", "leave_block_list", + "_Test Leave Block List") + self.assertTrue("2013-01-02" in + [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]) + + def test_get_applicable_block_dates_for_allowed_user(self): + webnotes.session.user = "test1@example.com" + webnotes.conn.set_value("Department", "_Test Department 1", "leave_block_list", + "_Test Leave Block List") + self.assertEquals([], [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03")]) + + def test_get_applicable_block_dates_all_lists(self): + webnotes.session.user = "test1@example.com" + webnotes.conn.set_value("Department", "_Test Department 1", "leave_block_list", + "_Test Leave Block List") + self.assertTrue("2013-01-02" in + [d.block_date for d in get_applicable_block_dates("2013-01-01", "2013-01-03", all_lists=True)]) + +test_dependencies = ["Employee"] + test_records = [[{ "doctype":"Leave Block List", "leave_block_list_name": "_Test Leave Block List", diff --git a/hr/doctype/leave_block_list_allow/leave_block_list_allow.txt b/hr/doctype/leave_block_list_allow/leave_block_list_allow.txt index 8709ff74c1e..4d73833d287 100644 --- a/hr/doctype/leave_block_list_allow/leave_block_list_allow.txt +++ b/hr/doctype/leave_block_list_allow/leave_block_list_allow.txt @@ -1,8 +1,8 @@ [ { - "creation": "2013-02-06 17:43:44", + "creation": "2013-02-14 17:37:38", "docstatus": 0, - "modified": "2013-02-14 17:15:45", + "modified": "2013-02-14 17:41:53", "modified_by": "Administrator", "owner": "Administrator" }, @@ -23,6 +23,7 @@ "parentfield": "fields", "parenttype": "DocType", "permlevel": 0, + "reqd": 1, "width": "200px" }, { diff --git a/hr/doctype/leave_block_list_date/leave_block_list_date.txt b/hr/doctype/leave_block_list_date/leave_block_list_date.txt index bf543ae5839..7c7ef38d4a8 100644 --- a/hr/doctype/leave_block_list_date/leave_block_list_date.txt +++ b/hr/doctype/leave_block_list_date/leave_block_list_date.txt @@ -1,8 +1,8 @@ [ { - "creation": "2013-02-05 11:48:25", + "creation": "2013-02-14 17:37:38", "docstatus": 0, - "modified": "2013-02-14 17:15:52", + "modified": "2013-02-14 17:41:44", "modified_by": "Administrator", "owner": "Administrator" }, @@ -19,6 +19,7 @@ "parentfield": "fields", "parenttype": "DocType", "permlevel": 0, + "reqd": 1, "width": "200px" }, { diff --git a/utilities/page/calendar/calendar.js b/utilities/page/calendar/calendar.js index cac5ec8265a..aab8f0b194b 100644 --- a/utilities/page/calendar/calendar.js +++ b/utilities/page/calendar/calendar.js @@ -20,6 +20,8 @@ // OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // +wn.provide("erpnext.calendar"); + pscript.onload_calendar = function(wrapper) { wn.ui.make_app_page({ parent: wrapper, @@ -31,7 +33,81 @@ pscript.onload_calendar = function(wrapper) { wn.require('lib/js/lib/fullcalendar/fullcalendar.js'); } -pscript.update_event = function(event) { +pscript.onshow_calendar = function(wrapper) { + if(!wrapper.setup_complete) { + erpnext.calendar.setup(wrapper); + } else { + $("#fullcalendar").fullCalendar("refetchEvents"); + } +} + +erpnext.calendar.setup = function(wrapper) { + wn.model.with_doctype("Event", function() { + $('
').appendTo($(wrapper).find('.layout-main')).fullCalendar({ + header: { + left: 'prev,next today', + center: 'title', + right: 'month,agendaWeek,agendaDay' + }, + editable: true, + selectable: true, + selectHelper: true, + events: function(start, end, callback) { + wn.call({ + method: 'utilities.page.calendar.calendar.get_events', + type: "GET", + args: { + start: dateutil.obj_to_str(start), + end: dateutil.obj_to_str(end), + company: wn.user.get_default("company")[0], + employee: wn.user.get_default("employee")[0] + }, + callback: function(r) { + var events = r.message; + $.each(events, function(i, d) { + d.editable = d.owner==user; + var options = erpnext.calendar.event_options[d.doctype]; + if(options && options.prepare) + options.prepare(d); + }); + callback(events); + } + }) + }, + eventClick: function(event, jsEvent, view) { + // edit event description or delete + var options = erpnext.calendar.event_options[event.doctype]; + if(options && options.click) + options.click(event); + }, + eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) { + erpnext.calendar.update_event(event); + }, + eventResize: function(event, dayDelta, minuteDelta, allDay, revertFunc) { + erpnext.calendar.update_event(event); + }, + select: function(startDate, endDate, allDay, jsEvent, view) { + if(jsEvent.day_clicked && view.name=="month") + return; + var event = wn.model.get_new_doc("Event"); + event.starts_on = wn.datetime.get_datetime_as_string(startDate); + event.ends_on = wn.datetime.get_datetime_as_string(endDate); + event.all_day = allDay ? 1 : 0; + wn.set_route("Form", "Event", event.name); + }, + dayClick: function(date, allDay, jsEvent, view) { + jsEvent.day_clicked = true; + $("#fullcalendar").fullCalendar("gotoDate", date) + return false; + } + }); + }); + + wrapper.setup_complete = true; + +} + +erpnext.calendar.update_event = function(event) { wn.model.remove_from_locals("Event", event.id); wn.call({ module: "utilities", @@ -40,6 +116,7 @@ pscript.update_event = function(event) { args: { "start": wn.datetime.get_datetime_as_string(event.start), "end": wn.datetime.get_datetime_as_string(event.end), + "all_day": event.allDay, "name": event.id }, callback: function(r) { @@ -50,57 +127,31 @@ pscript.update_event = function(event) { }); } - -pscript.onshow_calendar = function(wrapper) { - if(!wrapper.setup_complete) { - $('
').appendTo($(wrapper).find('.layout-main')).fullCalendar({ - header: { - left: 'prev,next today', - center: 'title', - right: 'month,agendaWeek,agendaDay' - }, - editable: true, - events: function(start, end, callback) { - wn.call({ - method: 'utilities.page.calendar.calendar.get_events', - type: "GET", - args: { - start: dateutil.obj_to_str(start), - end: dateutil.obj_to_str(end) - }, - callback: function(r) { - var events = r.message; - $.each(events, function(i, d) { - d.editable = d.owner==user; - d.allDay = false; - }); - callback(events); - } - }) - }, - dayClick: function(date, allDay, jsEvent, view) { - // if current date, show popup to create a new event - var ev = wn.model.create('Event') - ev.doc.set('start', date); - ev.doc.set('end', new Date(date)); - ev.doc.set('all_day', 1); - - }, - eventClick: function(calEvent, jsEvent, view) { - // edit event description or delete - wn.set_route("Form", "Event", calEvent.id); - }, - eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) { - pscript.update_event(event); - }, - eventResize: function(event, dayDelta, minuteDelta, allDay, revertFunc) { - pscript.update_event(event); +erpnext.calendar.event_options = { + "Leave Block List Date": { + prepare: function(d) { + d.color = "#aaa"; + } + }, + "Event": { + prepare: function(d) { + if(d.event_type=="Public") { + d.color = "#57AF5B"; } - }); - - wrapper.setup_complete = true; - } else { - $("#fullcalendar").fullCalendar("refetchEvents"); + }, + click: function(event) { + wn.set_route("Form", "Event", event.id); + } + }, + "Leave Application": { + prepare: function(d) { + d.color = "#4F9F96"; + }, + click: function(event) { + if(event.employee==wn.user.get_default("employee")[0]) { + wn.set_route("Form", "Leave Application", event.id); + } + } } } diff --git a/utilities/page/calendar/calendar.py b/utilities/page/calendar/calendar.py index 09b3297b992..06d4385569b 100644 --- a/utilities/page/calendar/calendar.py +++ b/utilities/page/calendar/calendar.py @@ -1,29 +1,83 @@ from __future__ import unicode_literals import webnotes +from webnotes import _ @webnotes.whitelist() -def get_events(start, end): +def get_events(start, end, employee=None, company=None): roles = webnotes.get_roles() events = webnotes.conn.sql("""select name as `id`, subject as title, - starts_on as `start`, ends_on as `end`, "Event" as doctype, owner - from tabEvent where event_date between %s and %s + starts_on as `start`, ends_on as `end`, "Event" as doctype, owner, + all_day as allDay, event_type + from tabEvent where ( + (starts_on between %s and %s) + or (ends_on between %s and %s) + ) and (event_type='Public' or owner=%s or exists(select * from `tabEvent User` where `tabEvent User`.parent=tabEvent.name and person=%s) or exists(select * from `tabEvent Role` where `tabEvent Role`.parent=tabEvent.name - and `tabEvent Role`.role in ('%s')))""" % ('%s', '%s', '%s', '%s', - "', '".join(roles)), (start, end, - webnotes.session.user, webnotes.session.user), as_dict=1, debug=1) - + and `tabEvent Role`.role in ('%s')))""" % ('%s', '%s', '%s', '%s', '%s', '%s', + "', '".join(roles)), (start, end, start, end, + webnotes.session.user, webnotes.session.user), as_dict=1) + + + if employee: + add_block_dates(events, start, end, employee, company) + add_department_leaves(events, start, end, employee, company) + return events - - block_days = webnotes.conn.sql("""select block_date as `start`, - name as `id`, reason as `title`, "Holiday List Block Date" as doctype, - where block_date between %s and %s - and """) - + +def add_department_leaves(events, start, end, employee, company): + department = webnotes.conn.get_value("Employee", employee, "department") + + if not department: + return + + # department leaves + department_employees = webnotes.conn.sql_list("select name from tabEmployee where department=%s", + department) + + for d in webnotes.conn.sql("""select name, from_date, to_date, employee_name, half_day, + status, employee + from `tabLeave Application` where + (from_date between %s and %s or to_date between %s and %s) + and docstatus < 2 + and status!="Rejected" + and employee in ('%s')""" % ("%s", "%s", "%s", "%s", "', '".join(department_employees)), + (start, end, start, end), as_dict=True): + events.append({ + "id": d.name, + "employee": d.employee, + "doctype": "Leave Application", + "start": d.from_date, + "end": d.to_date, + "allDay": True, + "status": d.status, + "title": _("Leave by") + " " + d.employee_name + \ + (d.half_day and _(" (Half Day)") or "") + }) + + +def add_block_dates(events, start, end, employee, company): + # block days + from hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates + + cnt = 0 + block_dates = get_applicable_block_dates(start, end, employee, company, all_lists=True) + + for block_date in block_dates: + events.append({ + "doctype": "Leave Block List Date", + "start": block_date.block_date, + "title": _("Leave Blocked") + ": " + block_date.reason, + "id": "_" + str(cnt), + "allDay": True + }) + cnt+=1 + + @webnotes.whitelist() def update_event(name, start, end): webnotes.conn.sql("""update tabEvent set starts_on=%s, ends_on=%s where