From afa98bb39b56b5fa9d077f57aa2b1a840d1be36c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:41:53 +0530 Subject: [PATCH 01/16] feat: Assign shift for period or create ongoing assignment --- .../shift_assignment/shift_assignment.json | 37 ++++-- .../shift_assignment/shift_assignment.py | 105 ++++++++++++------ 2 files changed, 100 insertions(+), 42 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.json b/erpnext/hr/doctype/shift_assignment/shift_assignment.json index 72cbba8a0dc..ce2a10f229f 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.json +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.json @@ -10,9 +10,11 @@ "employee", "employee_name", "shift_type", + "status", "column_break_3", "company", - "date", + "start_date", + "end_date", "shift_request", "department", "amended_from" @@ -59,12 +61,6 @@ "options": "Company", "reqd": 1 }, - { - "fieldname": "date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Date" - }, { "fieldname": "shift_request", "fieldtype": "Link", @@ -80,11 +76,36 @@ "options": "Shift Assignment", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "show_days": 1, + "show_seconds": 1 + }, + { + "allow_on_submit": 1, + "default": "Active", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Active\nInactive", + "show_days": 1, + "show_seconds": 1 } ], "is_submittable": 1, "links": [], - "modified": "2019-12-12 15:49:06.956901", + "modified": "2020-06-15 14:27:54.310773", "modified_by": "Administrator", "module": "HR", "name": "Shift Assignment", diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 40c78cdf072..e2bb98076f3 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -11,38 +11,64 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday from datetime import timedelta, datetime -class OverlapError(frappe.ValidationError): pass - class ShiftAssignment(Document): def validate(self): self.validate_overlapping_dates() + if self.end_date and self.end_date <= self.start_date: + frappe.throw(_("End Date should not be less than Start Date")) + def validate_overlapping_dates(self): - if not self.name: - self.name = "New Shift Assignment" + if not self.name: + self.name = "New Shift Assignment" - d = frappe.db.sql(""" - select - name, shift_type, date - from `tabShift Assignment` - where employee = %(employee)s and docstatus < 2 - and date = %(date)s - and name != %(name)s""", { - "employee": self.employee, - "shift_type": self.shift_type, - "date": self.date, - "name": self.name - }, as_dict = 1) + condition = """and ( + end_date is null + or + %(start_date)s between start_date and end_date + """ - for date_overlap in d: - if date_overlap['name']: - self.throw_overlap_error(date_overlap) + if self.end_date: + condition += """ or + %(end_date)s between start_date and end_date + or + start_date between %(start_date)s and %(end_date)s + ) """ + else: + condition += """ ) """ - def throw_overlap_error(self, d): - msg = _("Employee {0} has already applied for {1} on {2} : ").format(self.employee, - d['shift_type'], formatdate(d['date'])) \ - + """ {0}""".format(d["name"]) - frappe.throw(msg, OverlapError) + assigned_shifts = frappe.db.sql(""" + select name, shift_type, start_date ,end_date, docstatus, status + from `tabShift Assignment` + where + employee=%(employee)s and docstatus < 2 + and name != %(name)s + and status = "Active" + {0} + """.format(condition), { + "employee": self.employee, + "shift_type": self.shift_type, + "start_date": self.start_date, + "end_date": self.end_date, + "name": self.name + }, as_dict = 1) + + for shift in assigned_shifts: + if shift.name: + self.throw_overlap_error(shift) + + def throw_overlap_error(self, shift_details): + shift_details = frappe._dict(shift_details) + if shift_details.docstatus == 0: + msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + if shift_details.docstatus == 1 and shift_details.status == "Active": + msg = _("Employee {0} already have Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + if shift_details.start_date: + msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + if shift_details.end_date: + msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) + if msg: + frappe.throw(msg) @frappe.whitelist() def get_events(start, end, filters=None): @@ -62,19 +88,22 @@ def get_events(start, end, filters=None): return events def add_assignments(events, start, end, conditions=None): - query = """select name, date, employee_name, + query = """select name, start_date, end_date, employee_name, employee, docstatus from `tabShift Assignment` where - date <= %(date)s - and docstatus < 2""" + start_date >= %(start_date)s + and docstatus < 2""".format() if conditions: query += conditions - for d in frappe.db.sql(query, {"date":start, "date":end}, as_dict=True): + for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): + from pprint import pprint + pprint(d) e = { "name": d.name, "doctype": "Shift Assignment", - "date": d.date, + "start_date": d.start_date, + "end_date": d.end_date if d.end_date else nowdate(), "title": cstr(d.employee_name) + \ cstr(d.shift_type), "docstatus": d.docstatus @@ -92,7 +121,14 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ default_shift = frappe.db.get_value('Employee', employee, 'default_shift') - shift_type_name = frappe.db.get_value('Shift Assignment', {'employee':employee, 'date': for_date, 'docstatus': '1'}, 'shift_type') + shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) + + shift_type_name = shift_assignment_details[0] + + # if end_date present means that shift is over after end_date else it is a ongoing shift. + if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : + shift_type_name = None + if not shift_type_name and consider_default_shift: shift_type_name = default_shift if shift_type_name: @@ -117,12 +153,13 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals direction = '<' if next_shift_direction == 'reverse' else '>' sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc' dates = frappe.db.get_all('Shift Assignment', - 'date', - {'employee':employee, 'date':(direction, for_date), 'docstatus': '1'}, + 'start_date', + {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"}, as_list=True, limit=MAX_DAYS, order_by="date "+sort_order) + for date in dates: - shift_details = get_employee_shift(employee, date[0], consider_default_shift, None) + shift_details = get_employee_shift(employee, date.start_date, consider_default_shift, None) if shift_details: shift_type_name = shift_details.shift_type.name for_date = date[0] @@ -134,7 +171,7 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False): """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee """ - # write and verify a test case for midnight shift. + # write and verify a test case for midnight shift. prev_shift = curr_shift = next_shift = None curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward') if curr_shift: From dd46d19765f9b8b4f890863dc44e43e2c1cff377 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:44:04 +0530 Subject: [PATCH 02/16] feat: map event calendar to show ongoing shift assignment and fixed period assigment --- .../hr/doctype/shift_assignment/shift_assignment_calendar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js index c2c9bc073ad..17a986deb21 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js @@ -3,8 +3,8 @@ frappe.views.calendar["Shift Assignment"] = { field_map: { - "start": "date", - "end": "date", + "start": "start_date", + "end": "end_date", "id": "name", "docstatus": 1 }, From a6ec7e31d24b47dc866a9c361ec9ceddbcbf3079 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:44:44 +0530 Subject: [PATCH 03/16] test: shift assignment --- .../shift_assignment/test_shift_assignment.py | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 7fe80a236c6..4c3c1ed579e 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import nowdate, add_days test_dependencies = ["Shift Type"] @@ -20,8 +20,61 @@ class TestShiftAssignment(unittest.TestCase): "shift_type": "Day Shift", "company": "_Test Company", "employee": "_T-Employee-00001", - "date": nowdate() + "start_date": nowdate() }).insert() shift_assignment.submit() self.assertEqual(shift_assignment.docstatus, 1) + + def test_overlapping_for_ongoing_shift(self): + # shift should be Ongoing if Only start_date is present and status = Active + + shift_assignment_1 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": nowdate(), + "status": 'Active' + }).insert() + shift_assignment_1.submit() + + self.assertEqual(shift_assignment_1.docstatus, 1) + + shift_assignment = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": add_days(nowdate(), 2) + }) + + self.assertRaises(frappe.ValidationError, shift_assignment.save) + + def test_overlapping_for_fixed_period_shift(self): + # shift should is for Fixed period if Only start_date and end_date both are present and status = Active + + shift_assignment_1 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": nowdate(), + "end_date": add_days(nowdate(), 30), + "status": 'Active' + }).insert() + shift_assignment_1.submit() + + + # it should not allowed within period of any shift. + shift_assignment_3 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date":add_days(nowdate(), 10), + "end_date": add_days(nowdate(), 35), + "status": 'Active' + }) + + self.assertRaises(frappe.ValidationError, shift_assignment_3.save) \ No newline at end of file From b3e00de0ae0a03f8cac2c166350c922544f78da8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 17:37:41 +0530 Subject: [PATCH 04/16] refactor: Shift Request --- erpnext/hr/doctype/department/department.json | 94 +++- .../department_approver.py | 7 +- .../hr/doctype/shift_request/shift_request.js | 15 +- .../doctype/shift_request/shift_request.json | 529 +++++------------- .../hr/doctype/shift_request/shift_request.py | 44 +- .../shift_request/test_shift_request.py | 20 +- 6 files changed, 267 insertions(+), 442 deletions(-) diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json index a54c1d18e7c..dcb6a742b75 100644 --- a/erpnext/hr/doctype/department/department.json +++ b/erpnext/hr/doctype/department/department.json @@ -17,10 +17,10 @@ "payroll_cost_center", "column_break_9", "leave_block_list", - "leave_section", + "approvers", "leave_approvers", - "expense_section", "expense_approvers", + "shift_request_approver", "lft", "rgt", "old_parent" @@ -33,14 +33,18 @@ "label": "Department", "oldfieldname": "department_name", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "parent_department", "fieldtype": "Link", "in_list_view": 1, "label": "Parent Department", - "options": "Department" + "options": "Department", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -48,7 +52,9 @@ "in_standard_filter": 1, "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -56,17 +62,23 @@ "fieldname": "is_group", "fieldtype": "Check", "in_list_view": 1, - "label": "Is Group" + "label": "Is Group", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled" + "label": "Disabled", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_4", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "description": "Days for which Holidays are blocked for this department.", @@ -74,31 +86,25 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Leave Block List", - "options": "Leave Block List" + "options": "Leave Block List", + "show_days": 1, + "show_seconds": 1 }, { - "fieldname": "leave_section", - "fieldtype": "Section Break", - "label": "Leave Approvers" - }, - { - "description": "The first Leave Approver in the list will be set as the default Leave Approver.", "fieldname": "leave_approvers", "fieldtype": "Table", "label": "Leave Approver", - "options": "Department Approver" + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 }, { - "fieldname": "expense_section", - "fieldtype": "Section Break", - "label": "Expense Approvers" - }, - { - "description": "The first Expense Approver in the list will be set as the default Expense Approver.", "fieldname": "expense_approvers", "fieldtype": "Table", "label": "Expense Approver", - "options": "Department Approver" + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "lft", @@ -106,7 +112,9 @@ "hidden": 1, "label": "lft", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rgt", @@ -114,7 +122,9 @@ "hidden": 1, "label": "rgt", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "old_parent", @@ -122,28 +132,52 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Old Parent", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payroll_cost_center", "fieldtype": "Link", "label": "Payroll Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_9", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "description": "The first Approver in the list will be set as the default Approver.", + "fieldname": "approvers", + "fieldtype": "Section Break", + "label": "Approvers", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "shift_request_approver", + "fieldtype": "Table", + "label": "Shift Request Approver", + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-sitemap", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-05-05 18:49:28.503931", + "modified": "2020-06-23 15:42:00.563272", "modified_by": "Administrator", "module": "HR", "name": "Department", diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index afd54b83469..7bd8fd4aba3 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -15,7 +15,7 @@ class DepartmentApprover(Document): def get_approvers(doctype, txt, searchfield, start, page_len, filters): if not filters.get("employee"): - frappe.throw(_("Please select Employee Record first.")) + frappe.throw(_("Please select Employee first.")) approvers = [] department_details = {} @@ -41,9 +41,12 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" field_name = "Leave Approver" - else: + elif filters.get("doctype") == "Leave Application": parentfield = "expense_approvers" field_name = "Expense Approver" + else: + parentfield = "shift_request_approver" + field_name = "Approver" if department_list: for d in department_list: approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from diff --git a/erpnext/hr/doctype/shift_request/shift_request.js b/erpnext/hr/doctype/shift_request/shift_request.js index 1db7c7d10e5..b17a6f3845b 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.js +++ b/erpnext/hr/doctype/shift_request/shift_request.js @@ -2,7 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Shift Request', { - refresh: function(frm) { - - } + setup: function(frm) { + frm.set_query("approver", function() { + return { + query: "erpnext.hr.doctype.department_approver.department_approver.get_approvers", + filters: { + employee: frm.doc.employee, + doctype: frm.doc.doctype + } + }; + }); + frm.set_query("employee", erpnext.queries.employee); + }, }); diff --git a/erpnext/hr/doctype/shift_request/shift_request.json b/erpnext/hr/doctype/shift_request/shift_request.json index dd056470cd3..fee55dd14b3 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.json +++ b/erpnext/hr/doctype/shift_request/shift_request.json @@ -1,396 +1,175 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "HR-SHR-.YY.-.MM.-.#####", - "beta": 0, - "creation": "2018-04-13 16:32:27.974273", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "autoname": "HR-SHR-.YY.-.MM.-.#####", + "creation": "2018-04-13 16:32:27.974273", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "shift_type", + "employee", + "employee_name", + "department", + "status", + "column_break_4", + "company", + "approver", + "from_date", + "to_date", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shift_type", - "fieldtype": "Link", - "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": "Shift Type", - "length": 0, - "no_copy": 0, - "options": "Shift Type", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shift_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Shift Type", + "options": "Shift Type", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "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": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "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": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "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, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column 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, - "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 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 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": 1, - "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": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "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": "From Date", - "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 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "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": "To Date", - "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 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Shift Request", - "permlevel": 0, - "print_hide": 1, - "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, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Shift Request", + "print_hide": 1, + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Draft\nApproved\nRejected", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "approver", + "fieldtype": "Link", + "label": "Approver", + "options": "User", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:36.577448", - "modified_by": "Administrator", - "module": "HR", - "name": "Shift Request", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2020-06-23 15:56:44.536207", + "modified_by": "Administrator", + "module": "HR", + "name": "Shift Request", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "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": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "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": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index ff5de08ee5f..f39bdb8105e 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate +from erpnext.hr.doctype.department_approver.department_approver import get_approvers class OverlapError(frappe.ValidationError): pass @@ -14,15 +15,19 @@ class ShiftRequest(Document): def validate(self): self.validate_dates() self.validate_shift_request_overlap_dates() + self.validate_approver() def on_submit(self): - date_list = self.get_working_days(self.from_date, self.to_date) - for date in date_list: + if self.status not in ["Approved", "Rejected"]: + frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted")) + if self.status == "Approved": assignment_doc = frappe.new_doc("Shift Assignment") assignment_doc.company = self.company assignment_doc.shift_type = self.shift_type assignment_doc.employee = self.employee - assignment_doc.date = date + assignment_doc.start_date = self.from_date + if self.to_date: + assignment_doc.end_date = self.to_date assignment_doc.shift_request = self.name assignment_doc.insert() assignment_doc.submit() @@ -34,6 +39,13 @@ class ShiftRequest(Document): shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name']) shift_assignment_doc.cancel() + def validate_approver(self): + department = frappe.get_value("Employee", self.employee, "department") + # shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") + approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department)) + approvers = [approver[0] for approver in approvers] + if self.approver not in approvers: + frappe.throw(__("Only Approvers can Approve this Request.")) def validate_dates(self): if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): @@ -68,28 +80,4 @@ class ShiftRequest(Document): msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ + """ {0}""".format(d["name"]) - frappe.throw(msg, OverlapError) - - def get_working_days(self, start_date, end_date): - start_date, end_date = getdate(start_date), getdate(end_date) - - from datetime import timedelta - - date_list = [] - employee_holiday_list = [] - - employee_holidays = frappe.db.sql("""select holiday_date from `tabHoliday` - where parent in (select holiday_list from `tabEmployee` - where name = %s)""",self.employee,as_dict=1) - - for d in employee_holidays: - employee_holiday_list.append(d.holiday_date) - - reference_date = start_date - - while reference_date <= end_date: - if reference_date not in employee_holiday_list: - date_list.append(reference_date) - reference_date += timedelta(days=1) - - return date_list \ No newline at end of file + frappe.throw(msg, OverlapError) \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 1d0cf719c29..3dcfcbf4a5b 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import nowdate, add_days class TestShiftRequest(unittest.TestCase): def setUp(self): @@ -13,14 +13,20 @@ class TestShiftRequest(unittest.TestCase): frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) def test_make_shift_request(self): + department = frappe.get_value("Employee", "_T-Employee-00001", 'department') + set_shift_approver(department) + approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] + shift_request = frappe.get_doc({ "doctype": "Shift Request", "shift_type": "Day Shift", "company": "_Test Company", "employee": "_T-Employee-00001", "employee_name": "_Test Employee", - "start_date": nowdate(), - "end_date": nowdate() + "from_date": nowdate(), + "to_date": add_days(nowdate(), 10), + "approver": approver, + "status": "Approved" }) shift_request.insert() shift_request.submit() @@ -34,4 +40,10 @@ class TestShiftRequest(unittest.TestCase): self.assertEqual(shift_request.employee, employee) shift_request.cancel() shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')}) - self.assertEqual(shift_assignment_doc.docstatus, 2) \ No newline at end of file + self.assertEqual(shift_assignment_doc.docstatus, 2) + +def set_shift_approver(department): + department_doc = frappe.get_doc("Department", department) + department_doc.append('shift_request_approver',{'approver': "test1@example.com"}) + department_doc.save() + department_doc.reload() \ No newline at end of file From feef4a6c1e486b4d6b6a1176b4a748229c359f08 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:18:02 +0530 Subject: [PATCH 05/16] test: employee checkin --- .../hr/doctype/shift_assignment/shift_assignment.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index e2bb98076f3..296a86a2f96 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -121,13 +121,15 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ default_shift = frappe.db.get_value('Employee', employee, 'default_shift') + shift_type_name = None shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) - shift_type_name = shift_assignment_details[0] + if shift_assignment_details: + shift_type_name = shift_assignment_details[0] - # if end_date present means that shift is over after end_date else it is a ongoing shift. - if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : - shift_type_name = None + # if end_date present means that shift is over after end_date else it is a ongoing shift. + if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : + shift_type_name = None if not shift_type_name and consider_default_shift: shift_type_name = default_shift From 43ba7dd4c8a5e2d09b2c38de800e59e20ad4d579 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:18:56 +0530 Subject: [PATCH 06/16] feat: don't allow to request for default shift --- erpnext/hr/doctype/shift_request/shift_request.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index f39bdb8105e..9738c6c97b6 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -16,6 +16,7 @@ class ShiftRequest(Document): self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() + self.validate_default_shift() def on_submit(self): if self.status not in ["Approved", "Rejected"]: @@ -39,11 +40,17 @@ class ShiftRequest(Document): shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name']) shift_assignment_doc.cancel() + def validate_default_shift(self): + default_shift = frappe.get_value("Employee", self.employee, "default_shift") + if self.shift_type == default_shift: + frappe.throw(_("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type))) + def validate_approver(self): department = frappe.get_value("Employee", self.employee, "department") - # shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") + shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department)) approvers = [approver[0] for approver in approvers] + approvers.append(shift_approver) if self.approver not in approvers: frappe.throw(__("Only Approvers can Approve this Request.")) From 17e6d0918b4213f29f2500cd16a7f9232728e9ce Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:19:53 +0530 Subject: [PATCH 07/16] feat: Employee level Shift Request Approver --- .../department_approver.py | 4 ++- erpnext/hr/doctype/employee/employee.json | 34 ++++++++++++++++--- .../shift_assignment/shift_assignment.py | 21 ++++++------ .../hr/doctype/shift_request/shift_request.py | 3 +- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 7bd8fd4aba3..b5aa4ac6d44 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True) + employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True) employee_department = filters.get("department") or employee.department if employee_department: @@ -37,6 +37,8 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Expense Claim" and employee.expense_approver: approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name'])) + if filters.get("doctype") == "Shift Request" and employee.shift_request_approver: + approvers.append(frappe.db.get_value("User", employee.shift_request_approver, ['name', 'first_name', 'last_name'])) if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index f2afe065d1e..8c02e4f1d64 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -51,10 +51,14 @@ "column_break_31", "grade", "branch", + "approvers_section", + "expense_approver", + "leave_approver", + "column_break_45", + "shift_request_approver", "attendance_and_leave_details", "leave_policy", "attendance_device_id", - "leave_approver", "column_break_44", "holiday_list", "default_shift", @@ -62,7 +66,6 @@ "salary_mode", "payroll_cost_center", "column_break_52", - "expense_approver", "bank_name", "bank_ac_no", "health_insurance_section", @@ -806,14 +809,37 @@ "fieldname": "expense_approver", "fieldtype": "Link", "label": "Expense Approver", - "options": "User" + "options": "User", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "approvers_section", + "fieldtype": "Section Break", + "label": "Approvers", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "shift_request_approver", + "fieldtype": "Link", + "label": "Shift Request Approver", + "options": "User", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", "links": [], - "modified": "2020-07-03 21:28:04.109189", + "modified": "2020-07-28 01:36:04.109189", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 296a86a2f96..c81345da784 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -97,8 +97,6 @@ def add_assignments(events, start, end, conditions=None): query += conditions for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): - from pprint import pprint - pprint(d) e = { "name": d.name, "doctype": "Shift Assignment", @@ -155,17 +153,20 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals direction = '<' if next_shift_direction == 'reverse' else '>' sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc' dates = frappe.db.get_all('Shift Assignment', - 'start_date', + ['start_date', 'end_date'], {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"}, as_list=True, - limit=MAX_DAYS, order_by="date "+sort_order) + limit=MAX_DAYS, order_by="start_date "+sort_order) - for date in dates: - shift_details = get_employee_shift(employee, date.start_date, consider_default_shift, None) - if shift_details: - shift_type_name = shift_details.shift_type.name - for_date = date[0] - break + if dates: + for date in dates: + if date[1] and date[1] < for_date: + continue + shift_details = get_employee_shift(employee, date[0], consider_default_shift, None) + if shift_details: + shift_type_name = shift_details.shift_type.name + for_date = date[0] + break return get_shift_details(shift_type_name, for_date) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 9738c6c97b6..8a2e7eda6d4 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,7 +7,6 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.doctype.department_approver.department_approver import get_approvers class OverlapError(frappe.ValidationError): pass @@ -52,7 +51,7 @@ class ShiftRequest(Document): approvers = [approver[0] for approver in approvers] approvers.append(shift_approver) if self.approver not in approvers: - frappe.throw(__("Only Approvers can Approve this Request.")) + frappe.throw(_("Only Approvers can Approve this Request.")) def validate_dates(self): if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): From 3aa1101d5ef86c1a0070af3d7cf8a465ff6b005a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 28 Jul 2020 13:32:13 +0530 Subject: [PATCH 08/16] fix: requested changes --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 4 ++-- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index b5aa4ac6d44..e65c9f8ebae 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -46,7 +46,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): elif filters.get("doctype") == "Leave Application": parentfield = "expense_approvers" field_name = "Expense Approver" - else: + elif filters.get("doctype") == "Shift Request": parentfield = "shift_request_approver" field_name = "Approver" if department_list: diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index c81345da784..f25d39b78f2 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -16,7 +16,7 @@ class ShiftAssignment(Document): self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date should not be less than Start Date")) + frappe.throw(_("End Date must not be greater than Start Date")) def validate_overlapping_dates(self): if not self.name: @@ -62,7 +62,7 @@ class ShiftAssignment(Document): if shift_details.docstatus == 0: msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) if shift_details.docstatus == 1 and shift_details.status == "Active": - msg = _("Employee {0} already have Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} already has Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) if shift_details.start_date: msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) if shift_details.end_date: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index 19735648aa9..dfe094e6b78 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -79,7 +79,7 @@ class ShiftType(Document): mark_attendance(employee, date, 'Absent', self.name) def get_assigned_employee(self, from_date=None, consider_default_shift=False): - filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} + filters = {'start_date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} if not from_date: del filters['date'] assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) From b48110caf13c7c1c9165c7e51490f2baf6dec7d7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 10 Aug 2020 14:17:31 +0530 Subject: [PATCH 09/16] Update erpnext/hr/doctype/shift_assignment/shift_assignment.py Co-authored-by: Marica --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index f25d39b78f2..f5bd0d068e9 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -16,7 +16,7 @@ class ShiftAssignment(Document): self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date must not be greater than Start Date")) + frappe.throw(_("End Date must not be lesser than Start Date")) def validate_overlapping_dates(self): if not self.name: From d5c921e358a3c9a0c11d9b4dc5513b35b68c00da Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 10 Aug 2020 14:28:55 +0530 Subject: [PATCH 10/16] Fix: Requested Changes --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index e65c9f8ebae..6626ece9634 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -43,7 +43,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" field_name = "Leave Approver" - elif filters.get("doctype") == "Leave Application": + elif filters.get("doctype") == "Expense Claim": parentfield = "expense_approvers" field_name = "Expense Approver" elif filters.get("doctype") == "Shift Request": From dcf598dc2d30adc45edf9750080a1e7acb873cbd Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 10 Aug 2020 18:03:12 +0530 Subject: [PATCH 11/16] Fix: requested Changes --- .../shift_assignment/shift_assignment.py | 8 ++-- .../doctype/shift_request/shift_request.json | 48 ++++++------------- .../hr/doctype/shift_request/shift_request.py | 2 + erpnext/hr/doctype/shift_type/shift_type.py | 7 ++- 4 files changed, 24 insertions(+), 41 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index f5bd0d068e9..20553b9a204 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -60,15 +60,17 @@ class ShiftAssignment(Document): def throw_overlap_error(self, shift_details): shift_details = frappe._dict(shift_details) if shift_details.docstatus == 0: - msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} has already applied for {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.docstatus == 1 and shift_details.status == "Active": - msg = _("Employee {0} already has Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.start_date: msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + title = "Ongoing Shift" if shift_details.end_date: msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) + title = "Active Shift" if msg: - frappe.throw(msg) + frappe.throw(msg, title=title) @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/hr/doctype/shift_request/shift_request.json b/erpnext/hr/doctype/shift_request/shift_request.json index fee55dd14b3..64cbdfff7da 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.json +++ b/erpnext/hr/doctype/shift_request/shift_request.json @@ -26,9 +26,7 @@ "in_list_view": 1, "label": "Shift Type", "options": "Shift Type", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "employee", @@ -36,18 +34,14 @@ "in_list_view": 1, "label": "Employee", "options": "Employee", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", "label": "Employee Name", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "employee.department", @@ -55,15 +49,11 @@ "fieldtype": "Link", "label": "Department", "options": "Department", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_4", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "company", @@ -71,24 +61,18 @@ "in_list_view": 1, "label": "Company", "options": "Company", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "from_date", "fieldtype": "Date", "label": "From Date", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "to_date", "fieldtype": "Date", - "label": "To Date", - "show_days": 1, - "show_seconds": 1 + "label": "To Date" }, { "fieldname": "amended_from", @@ -97,9 +81,7 @@ "no_copy": 1, "options": "Shift Request", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "Draft", @@ -107,23 +89,21 @@ "fieldtype": "Select", "label": "Status", "options": "Draft\nApproved\nRejected", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { + "fetch_from": "employee.shift_request_approver", + "fetch_if_empty": 1, "fieldname": "approver", "fieldtype": "Link", "label": "Approver", "options": "User", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-06-23 15:56:44.536207", + "modified": "2020-08-10 17:59:31.550558", "modified_by": "Administrator", "module": "HR", "name": "Shift Request", diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 8a2e7eda6d4..1c2801bf08f 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -32,6 +32,8 @@ class ShiftRequest(Document): assignment_doc.insert() assignment_doc.submit() + frappe.msgprint(_("Shift Assignment: {0} created for Employee: {1}").format(frappe.bold(assignment_doc.name), frappe.bold(self.employee))) + def on_cancel(self): shift_assignment_list = frappe.get_list("Shift Assignment", {'employee': self.employee, 'shift_request': self.name}) if shift_assignment_list: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index dfe094e6b78..dd08d31b015 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -78,10 +78,9 @@ class ShiftType(Document): if shift_details and shift_details.shift_type.name == self.name: mark_attendance(employee, date, 'Absent', self.name) - def get_assigned_employee(self, from_date=None, consider_default_shift=False): - filters = {'start_date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} - if not from_date: - del filters['date'] + def get_assigned_employee(self, consider_default_shift=False): + filters = {'shift_type': self.name, 'docstatus': '1'} + assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) assigned_employees = [x[0] for x in assigned_employees] From 00a8081aba33896f4c4cbc4bffa7af40ac94e761 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 11 Aug 2020 13:39:27 +0530 Subject: [PATCH 12/16] Fix: button label --- erpnext/hr/doctype/shift_type/shift_type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.js b/erpnext/hr/doctype/shift_type/shift_type.js index e6335456303..ba53312bcef 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.js +++ b/erpnext/hr/doctype/shift_type/shift_type.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Shift Type', { refresh: function(frm) { frm.add_custom_button( - 'Mark Auto Attendance', + 'Mark Attendance', () => frm.call({ doc: frm.doc, method: 'process_auto_attendance', From 74f0a1ab7cfb62aa9531dd642dae786bb2f8e005 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 11 Aug 2020 19:00:43 +0530 Subject: [PATCH 13/16] fix: changes Requested --- .../doctype/department_approver/department_approver.py | 2 +- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 9 +++------ erpnext/hr/doctype/shift_type/shift_type.py | 6 ++++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 6626ece9634..9b2de0e1cbc 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -48,7 +48,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): field_name = "Expense Approver" elif filters.get("doctype") == "Shift Request": parentfield = "shift_request_approver" - field_name = "Approver" + field_name = "Shift Request Approver" if department_list: for d in department_list: approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 20553b9a204..4f5b59b7e2a 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -41,7 +41,7 @@ class ShiftAssignment(Document): select name, shift_type, start_date ,end_date, docstatus, status from `tabShift Assignment` where - employee=%(employee)s and docstatus < 2 + employee=%(employee)s and docstatus = 1 and name != %(name)s and status = "Active" {0} @@ -53,14 +53,11 @@ class ShiftAssignment(Document): "name": self.name }, as_dict = 1) - for shift in assigned_shifts: - if shift.name: - self.throw_overlap_error(shift) + if len(assigned_shifts): + self.throw_overlap_error(assigned_shifts[0]) def throw_overlap_error(self, shift_details): shift_details = frappe._dict(shift_details) - if shift_details.docstatus == 0: - msg = _("Employee {0} has already applied for {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.docstatus == 1 and shift_details.status == "Active": msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.start_date: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index dd08d31b015..054e7e3688e 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -78,8 +78,10 @@ class ShiftType(Document): if shift_details and shift_details.shift_type.name == self.name: mark_attendance(employee, date, 'Absent', self.name) - def get_assigned_employee(self, consider_default_shift=False): - filters = {'shift_type': self.name, 'docstatus': '1'} + def get_assigned_employee(self, from_date=None, consider_default_shift=False): + filters = {'start_date':('>', from_date), 'shift_type': self.name, 'docstatus': '1'} + if not from_date: + del filters["start_date"] assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) assigned_employees = [x[0] for x in assigned_employees] From c2710e05ce97f65b602ab2757293b2d8ebb4c611 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 12 Aug 2020 13:35:25 +0530 Subject: [PATCH 14/16] fix: showing on_going shift on calendar --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 4f5b59b7e2a..f8b73349c19 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -91,11 +91,13 @@ def add_assignments(events, start, end, conditions=None): employee, docstatus from `tabShift Assignment` where start_date >= %(start_date)s - and docstatus < 2""".format() + or end_date <= %(end_date)s + or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + and docstatus = 1""" if conditions: query += conditions - for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): + for d in frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True): e = { "name": d.name, "doctype": "Shift Assignment", From f096ba4998b056fe89d72250a748785bdf48a78c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 13 Aug 2020 14:06:20 +0530 Subject: [PATCH 15/16] patch: Old Shift Assignment --- erpnext/patches.txt | 1 + .../update_start_end_date_for_old_shift_assignment.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 361fe8352ab..e17e949b3bc 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -721,3 +721,4 @@ erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail +erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py new file mode 100644 index 00000000000..e9dafd41625 --- /dev/null +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -0,0 +1,10 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + frappe.reload_doc('hr', 'doctype', 'shift_assignment') + frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date, status='Inactive' where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") \ No newline at end of file From 13be09e883baf8d5021b28aec0f2129116073ef9 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 17 Aug 2020 13:43:33 +0530 Subject: [PATCH 16/16] Update update_start_end_date_for_old_shift_assignment.py --- .../v13_0/update_start_end_date_for_old_shift_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py index e9dafd41625..7c07b987f30 100644 --- a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -7,4 +7,4 @@ import frappe def execute(): frappe.reload_doc('hr', 'doctype', 'shift_assignment') - frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date, status='Inactive' where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") \ No newline at end of file + frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date where date IS NOT NULL and start_date IS NULL and end_date IS NULL;")