From 95c5c055f890a20a2ee0a97194ea002345c95526 Mon Sep 17 00:00:00 2001 From: Ranjith Kurungadam Date: Mon, 7 May 2018 12:43:08 +0530 Subject: [PATCH] Optional leave (#13931) * Optional Holiday - move holiday list from leave type to leave period * Optional Leave - validate on Leave Application * test Optional Leaves --- .../leave_application/leave_application.py | 20 ++- .../test_leave_application.py | 60 +++---- .../hr/doctype/leave_period/leave_period.json | 50 ++++-- erpnext/hr/doctype/leave_type/leave_type.json | 149 ++++-------------- 4 files changed, 117 insertions(+), 162 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index b33bd5444e..23514e16e2 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,8 +5,8 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ - comma_or, get_fullname -from erpnext.hr.utils import set_employee_name + comma_or, get_fullname, add_days +from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -15,6 +15,7 @@ class OverlapError(frappe.ValidationError): pass class InvalidLeaveApproverError(frappe.ValidationError): pass class LeaveApproverIdentityError(frappe.ValidationError): pass class AttendanceAlreadyMarkedError(frappe.ValidationError): pass +class NotAnOptionalHoliday(frappe.ValidationError): pass from frappe.model.document import Document class LeaveApplication(Document): @@ -31,6 +32,8 @@ class LeaveApplication(Document): self.validate_block_days() self.validate_salary_processed_days() self.validate_attendance() + if frappe.db.get_value("Leave Type", self.leave_type, 'is_optional_leave'): + self.validate_optional_leave() def on_update(self): if self.status == "Open" and self.docstatus < 1: @@ -207,6 +210,19 @@ class LeaveApplication(Document): frappe.throw(_("Attendance for employee {0} is already marked for this day").format(self.employee), AttendanceAlreadyMarkedError) + def validate_optional_leave(self): + leave_period = get_leave_period(self.from_date, self.to_date, self.company) + if not leave_period: + frappe.throw(_("Cannot find active Leave Period")) + optional_holiday_list = frappe.db.get_value("Leave Period", leave_period[0]["name"], "optional_holiday_list") + if not optional_holiday_list: + frappe.throw(_("Optional Holiday List not set for leave period {0}").format(leave_period[0]["name"])) + day = getdate(self.from_date) + while day <= getdate(self.to_date): + if not frappe.db.exists({"doctype": "Holiday", "parent": optional_holiday_list, "holiday_date": day}): + frappe.throw(_("{0} is not in Optional Holiday List").format(formatdate(day)), NotAnOptionalHoliday) + day = add_days(day, 1) + def notify_employee(self): employee = frappe.get_doc("Employee", self.employee) if not employee.user_id: diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 424da90791..e71357c2ed 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -5,8 +5,9 @@ from __future__ import unicode_literals import frappe import unittest -from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError +from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on from frappe.permissions import clear_user_permissions_for_doctype +from frappe.utils import add_days, nowdate test_dependencies = ["Leave Allocation", "Leave Block List"] @@ -225,55 +226,54 @@ class TestLeaveApplication(unittest.TestCase): frappe.db.set_value("Leave Block List", "_Test Leave Block List", "applies_to_all_departments", 0) - + def test_optional_leave(self): - '''''' leave_period = get_leave_period() - today = get_today() - + today = nowdate() + from datetime import date holiday_list = frappe.get_doc(dict( doctype = 'Holiday List', - name = 'test holiday list for optional holiday', - from_date = year_start_date(), - to_date = year_end_date(), + holiday_list_name = 'test holiday list for optional holiday', + from_date = date(date.today().year, 1, 1), + to_date = date(date.today().year, 12, 31), holidays = [ dict(holiday_date = today, description = 'test') ] - )) + )).insert() employee = get_employee() - - frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list) - + + frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list.name) + leave_type = frappe.get_doc(dict( leave_type_name = 'Test Optional Type', doctype = 'Leave Type', - is_optional_leave = 1, - holiday_list = holiday_list + is_optional_leave = 1 )).insert() - + allocate_leaves(employee, leave_period, leave_type.name, 10) - - date = get_today() - 1 - + + date = add_days(today, - 1) + leave_application = frappe.get_doc(dict( doctype = 'Leave Application', - employee = employee, + employee = employee.name, leave_type = leave_type.name, from_date = date, to_date = date, )) - + # can only apply on optional holidays self.assertTrue(NotAnOptionalHoliday, leave_application.insert) - + leave_application.from_date = today leave_application.to_date = today + leave_application.status = "Approved" leave_application.insert() leave_application.submit() - + # check leave balance is reduced - self.assertEqual(get_leave_balance(employee, leave_period, leave_type.name), 9) - + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, today), 9) + def test_leaves_allowed(self): # TODO: test cannot allocate more than max leaves pass @@ -285,7 +285,7 @@ class TestLeaveApplication(unittest.TestCase): def test_max_continuous_leaves(self): # TODO: test cannot take continuous leaves more than pass - + def test_earned_leave(self): leave_period = get_leave_period() employee = get_employee() @@ -297,14 +297,14 @@ class TestLeaveApplication(unittest.TestCase): earned_leave_frequency = 'Monthly', rounding = 0.5 )).insert() - + allocate_leaves(employee, leave_period, leave_type.name, 0, eligible_leaves = 12) - + # this method will be called by scheduler allocate_earned_leaves(leave_type.name, leave_period, as_on = half_of_leave_period) - + self.assertEqual(get_leave_balance(employee, leave_period, leave_type.name), 6) - + def make_allocation_record(employee=None, leave_type=None): frappe.db.sql("delete from `tabLeave Allocation`") @@ -319,4 +319,4 @@ def make_allocation_record(employee=None, leave_type=None): }) allocation.insert(ignore_permissions=True) - allocation.submit() \ No newline at end of file + allocation.submit() diff --git a/erpnext/hr/doctype/leave_period/leave_period.json b/erpnext/hr/doctype/leave_period/leave_period.json index 946ceec238..516d52d975 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.json +++ b/erpnext/hr/doctype/leave_period/leave_period.json @@ -41,7 +41,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -72,7 +71,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -102,7 +100,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -134,7 +131,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -165,7 +161,37 @@ "reqd": 0, "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": "optional_holiday_list", + "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": "Holiday List for Optional Leave", + "length": 0, + "no_copy": 0, + "options": "Holiday List", + "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 }, { @@ -196,7 +222,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -227,7 +252,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 }, { @@ -291,7 +314,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -323,7 +345,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -355,7 +376,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -386,7 +406,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -416,7 +435,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -447,7 +465,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -461,7 +478,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-14 13:29:57.066314", + "modified": "2018-05-04 18:25:06.719932", "modified_by": "Administrator", "module": "HR", "name": "Leave Period", @@ -470,6 +487,7 @@ "permissions": [ { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -489,6 +507,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -508,6 +527,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 1d1aef2045..aad7e7b9c5 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -41,7 +41,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -73,7 +72,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -104,7 +102,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -136,7 +133,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -166,7 +162,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -198,7 +193,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -228,7 +222,36 @@ "reqd": 0, "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": "is_optional_leave", + "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 Optional Leave", + "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 }, { @@ -258,7 +281,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -288,7 +310,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -319,7 +340,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -350,7 +370,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -382,7 +401,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -413,7 +431,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -445,7 +462,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -478,102 +494,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "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": "Optional Leave", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_optional_leave", - "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 Optional Leave", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "is_optional_leave", - "fieldname": "holiday_list", - "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": "Holiday List", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "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, - "translatable": 0, "unique": 0 }, { @@ -604,7 +524,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -635,7 +554,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -668,7 +586,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -701,7 +618,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -716,7 +632,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-14 14:36:46.824289", + "modified": "2018-05-03 19:42:23.852331", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", @@ -724,6 +640,7 @@ "permissions": [ { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -743,6 +660,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -762,6 +680,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0,