From 66e459b35d882fe36e282651c5e3fbf4d45c5735 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Thu, 30 May 2019 16:12:34 +0530 Subject: [PATCH] fix(auto attendance): changes as requested in review > removed unused field from shift assignment > renamed 'biometric_rf_id' to 'attendance_device_id' > added more test cases > few other minor changes after demo --- .../hr/doctype/attendance/test_attendance.py | 11 +- erpnext/hr/doctype/employee/employee.json | 9 +- erpnext/hr/doctype/employee/employee.py | 4 +- .../employee_attendance_log.py | 16 +- .../test_employee_attendance_log.py | 10 +- .../hr/doctype/holiday_list/holiday_list.py | 23 - .../doctype/holiday_list/test_holiday_list.py | 48 +- erpnext/hr/doctype/hr_settings/hr_settings.py | 15 +- .../shift_assignment/shift_assignment.json | 521 ++++-------------- .../shift_assignment/shift_assignment.py | 6 +- erpnext/hr/doctype/shift_type/shift_type.json | 56 +- 11 files changed, 202 insertions(+), 517 deletions(-) diff --git a/erpnext/hr/doctype/attendance/test_attendance.py b/erpnext/hr/doctype/attendance/test_attendance.py index 7fe020a18e..35d1126dc1 100644 --- a/erpnext/hr/doctype/attendance/test_attendance.py +++ b/erpnext/hr/doctype/attendance/test_attendance.py @@ -4,8 +4,17 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import nowdate test_records = frappe.get_test_records('Attendance') class TestAttendance(unittest.TestCase): - pass + def test_mark_absent(self): + from erpnext.hr.doctype.employee.test_employee import make_employee + employee = make_employee("test_mark_absent@example.com") + date = nowdate() + frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date}) + from erpnext.hr.doctype.attendance.attendance import mark_absent + attendance = mark_absent(employee, date) + fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'}) + self.assertEqual(attendance, fetch_attendance) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index d1384188e0..188d460fb3 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -16,7 +16,7 @@ "middle_name", "last_name", "employee_name", - "biometric_rf_id", + "attendance_device_id", "image", "column_break1", "company", @@ -511,6 +511,7 @@ "options": "Email" }, { + "default": "0", "fieldname": "unsubscribed", "fieldtype": "Check", "label": "Unsubscribed" @@ -749,9 +750,9 @@ "label": "Old Parent" }, { - "fieldname": "biometric_rf_id", + "fieldname": "attendance_device_id", "fieldtype": "Data", - "label": "Biometric/RF tag ID ", + "label": "Attendance Device ID (Biometric/RF tag ID)", "no_copy": 1, "unique": 1 } @@ -759,7 +760,7 @@ "icon": "fa fa-user", "idx": 24, "image_field": "image", - "modified": "2019-05-08 14:32:06.443825", + "modified": "2019-05-29 17:33:11.988538", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 48957e5819..63ba57385a 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -319,12 +319,12 @@ def get_holiday_list_for_employee(employee, raise_exception=True): return holiday_list -def is_holiday(employee, date=None): +def is_holiday(employee, date=None, raise_exception=True): '''Returns True if given Employee has an holiday on the given date :param employee: Employee `name` :param date: Date to check. Will check for today if None''' - holiday_list = get_holiday_list_for_employee(employee) + holiday_list = get_holiday_list_for_employee(employee, raise_exception) if not date: date = today() diff --git a/erpnext/hr/doctype/employee_attendance_log/employee_attendance_log.py b/erpnext/hr/doctype/employee_attendance_log/employee_attendance_log.py index 3932f8294b..b25f9b9aac 100644 --- a/erpnext/hr/doctype/employee_attendance_log/employee_attendance_log.py +++ b/erpnext/hr/doctype/employee_attendance_log/employee_attendance_log.py @@ -15,23 +15,24 @@ class EmployeeAttendanceLog(Document): @frappe.whitelist() -def add_log_based_on_biometric_rf_id(biometric_rf_id, timestamp, device_id=None, log_type=None): - """Finds the relevant Employee using the biometric_rf_id and creates a Employee Attendance Log. +def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=None, log_type=None, employee_fieldname='attendance_device_id'): + """Finds the relevant Employee using the employee field value and creates a Employee Attendance Log. - :param biometric_rf_id: The Biometric/RF tag ID as set up in Employee DocType. + :param employee_field_value: The value to look for in employee field. :param timestamp: The timestamp of the Log. Currently expected in the following format as string: '2019-05-08 10:48:08.000000' :param device_id: (optional)Location / Device ID. A short string is expected. :param log_type: (optional)Direction of the Punch if available (IN/OUT). + :param employee_fieldname: (Default: attendance_device_id)Name of the field in Employee DocType based on which employee lookup will happen. """ - if not biometric_rf_id or not timestamp: - frappe.throw(_("'biometric_rf_id' and 'timestamp' are required.")) + if not employee_field_value or not timestamp: + frappe.throw(_("'employee_field_value' and 'timestamp' are required.")) - employee = frappe.db.get_values("Employee", {"biometric_rf_id": biometric_rf_id}, ["name", "employee_name", "biometric_rf_id"], as_dict=True) + employee = frappe.db.get_values("Employee", {employee_fieldname: employee_field_value}, ["name", "employee_name", employee_fieldname], as_dict=True) if employee: employee = employee[0] else: - frappe.throw(_("No Employee found for the given 'biometric_rf_id':{}.").format(biometric_rf_id)) + frappe.throw(_("No Employee found for the given employee field value. '{}': {}").format(employee_fieldname,employee_field_value)) doc = frappe.new_doc("Employee Attendance Log") doc.employee = employee.name @@ -40,7 +41,6 @@ def add_log_based_on_biometric_rf_id(biometric_rf_id, timestamp, device_id=None, doc.device_id = device_id doc.log_type = log_type doc.insert() - frappe.db.commit() return doc diff --git a/erpnext/hr/doctype/employee_attendance_log/test_employee_attendance_log.py b/erpnext/hr/doctype/employee_attendance_log/test_employee_attendance_log.py index fdc63d6678..d1fcf0c55f 100644 --- a/erpnext/hr/doctype/employee_attendance_log/test_employee_attendance_log.py +++ b/erpnext/hr/doctype/employee_attendance_log/test_employee_attendance_log.py @@ -8,18 +8,18 @@ from frappe.utils import now_datetime, nowdate, to_timedelta import unittest from datetime import timedelta -from erpnext.hr.doctype.employee_attendance_log.employee_attendance_log import add_log_based_on_biometric_rf_id, mark_attendance_and_link_log +from erpnext.hr.doctype.employee_attendance_log.employee_attendance_log import add_log_based_on_employee_field, mark_attendance_and_link_log from erpnext.hr.doctype.employee.test_employee import make_employee class TestEmployeeAttendanceLog(unittest.TestCase): - def test_add_log_based_on_biometric_rf_id(self): - employee = make_employee("test_add_log_based_on_biometric_rf_id@example.com") + def test_add_log_based_on_employee_field(self): + employee = make_employee("test_add_log_based_on_employee_field@example.com") employee = frappe.get_doc("Employee", employee) - employee.biometric_rf_id = '3344' + employee.attendance_device_id = '3344' employee.save() time_now = now_datetime().__str__()[:-7] - employee_attendance_log = add_log_based_on_biometric_rf_id('3344', time_now, 'mumbai_first_floor', 'IN') + employee_attendance_log = add_log_based_on_employee_field('3344', time_now, 'mumbai_first_floor', 'IN') self.assertEqual(employee_attendance_log.employee, employee.name) self.assertEqual(employee_attendance_log.time, time_now) self.assertEqual(employee_attendance_log.device_id, 'mumbai_first_floor') diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 453320fb27..e475e6e95b 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -84,26 +84,3 @@ def get_events(start, end, filters=None): fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'], filters = filters, update={"allDay": 1}) - -def get_holiday_list(employee): - employee_holiday = frappe.db.get_all('Employee', fields=['name', 'holiday_list', 'company'], filters={'name':employee}) - if not employee_holiday: - frappe.throw(_("Employee not found.")) - if employee_holiday[0].holiday_list: - return employee_holiday[0].holiday_list - else: - company_holiday = frappe.db.get_all('Company', fields=['name', 'default_holiday_list'], filters={'name':employee_holiday[0].company}) - if company_holiday[0].default_holiday_list: - return company_holiday[0].default_holiday_list - return None - -def is_holiday(holiday_list, for_date): - """Returns true if the given date is a holiday in the given holiday list - """ - holiday = frappe.get_value('Holiday', { - 'parent': holiday_list, - 'parentfield': 'holidays', - 'parenttype': 'Holiday List', - 'holiday_date': for_date - }, 'name') - return bool(holiday) \ No newline at end of file diff --git a/erpnext/hr/doctype/holiday_list/test_holiday_list.py b/erpnext/hr/doctype/holiday_list/test_holiday_list.py index 33a24d14f7..2d2cc0b848 100644 --- a/erpnext/hr/doctype/holiday_list/test_holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/test_holiday_list.py @@ -6,39 +6,27 @@ import frappe import unittest from frappe.utils import getdate from datetime import timedelta -from erpnext.hr.doctype.employee.test_employee import make_employee class TestHolidayList(unittest.TestCase): - def test_get_holiday_list(self): - holiday_list = make_holiday_list("test_get_holiday_list") - employee = make_employee("test_get_holiday_list@example.com") - employee = frappe.get_doc("Employee", employee) - employee.holiday_list = None - employee.save() - company = frappe.get_doc("Company", employee.company) - company_default_holiday_list = company.default_holiday_list - - from erpnext.hr.doctype.holiday_list.holiday_list import get_holiday_list - holiday_list_name = get_holiday_list(employee.name) - self.assertEqual(holiday_list_name, company_default_holiday_list) - - employee.holiday_list = holiday_list.name - employee.save() - holiday_list_name = get_holiday_list(employee.name) - self.assertEqual(holiday_list_name, holiday_list.name) - + def test_holiday_list(self): + today_date = getdate() + test_holiday_dates = [today_date-timedelta(days=5), today_date-timedelta(days=4)] + holiday_list = make_holiday_list("test_is_holiday", + holiday_dates=[ + {'holiday_date': test_holiday_dates[0], 'description': 'test holiday'}, + {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'} + ]) + fetched_holiday_list = frappe.get_value('Holiday List', holiday_list.name) + self.assertEqual(holiday_list.name, fetched_holiday_list) def make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getdate(), holiday_dates=None): - if not frappe.db.get_value("Holiday List", name): - doc = frappe.get_doc({ - "doctype": "Holiday List", - "holiday_list_name": name, - "from_date" : from_date, - "to_date" : to_date - }).insert() - doc.holidays = holiday_dates - doc.save() - else: - doc = frappe.get_doc("Holiday List", name) + frappe.delete_doc_if_exists("Holiday List", name, force=1) + doc = frappe.get_doc({ + "doctype": "Holiday List", + "holiday_list_name": name, + "from_date" : from_date, + "to_date" : to_date, + "holidays" : holiday_dates + }).insert() return doc diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.py b/erpnext/hr/doctype/hr_settings/hr_settings.py index afd90f7b58..9511ff1717 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.py +++ b/erpnext/hr/doctype/hr_settings/hr_settings.py @@ -12,7 +12,7 @@ from frappe import _ from frappe.model.document import Document from erpnext.hr.doctype.shift_assignment.shift_assignment import get_employee_shift_timings, get_employee_shift from erpnext.hr.doctype.employee_attendance_log.employee_attendance_log import mark_attendance_and_link_log -from erpnext.hr.doctype.holiday_list.holiday_list import get_holiday_list +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.attendance.attendance import mark_absent class HRSettings(Document): @@ -79,9 +79,9 @@ def process_single_employee_logs(logs, hr_settings=None): while logs and logs[0].time <= actual_shift_end: single_shift_logs.append(logs.pop(0)) process_single_employee_shift_logs(single_shift_logs, shift_details) - mark_absent_for_days_with_no_attendance(last_log.employee, employee_last_sync, hr_settings) + mark_absent_for_dates_with_no_attendance(last_log.employee, employee_last_sync, hr_settings) -def mark_absent_for_days_with_no_attendance(employee, employee_last_sync, hr_settings=None): +def mark_absent_for_dates_with_no_attendance(employee, employee_last_sync, hr_settings=None): """Marks Absents for the given employee on working days which have no attendance marked. The Absent is marked starting from one shift before the employee_last_sync going back to 'hr_settings.process_attendance_after' or employee creation date. @@ -100,7 +100,7 @@ def mark_absent_for_days_with_no_attendance(employee, employee_last_sync, hr_set if prev_shift: end_date = prev_shift.start_datetime.date() elif hr_settings.attendance_for_employee_without_shift == 'At least one Employee Attendance Log per day as present': - for date in get_filtered_date_list(employee, "All Dates", start_date, employee_last_sync.date(), True, get_holiday_list(employee)): + for date in get_filtered_date_list(employee, "All Dates", start_date, employee_last_sync.date(), True, get_holiday_list_for_employee(employee, False)): mark_absent(employee, date) return else: @@ -111,7 +111,7 @@ def mark_absent_for_days_with_no_attendance(employee, employee_last_sync, hr_set if get_employee_shift(employee, date, consider_default_shift): mark_absent(employee, date) elif hr_settings.attendance_for_employee_without_shift == 'At least one Employee Attendance Log per day as present': - for date in get_filtered_date_list(employee, "All Dates", start_date, employee_last_sync.date(), True, get_holiday_list(employee)): + for date in get_filtered_date_list(employee, "All Dates", start_date, employee_last_sync.date(), True, get_holiday_list_for_employee(employee, False)): mark_absent(employee, date) else: for date in get_filtered_date_list(employee, "Assigned Shifts", start_date, end_date): @@ -155,9 +155,12 @@ def get_filtered_date_list(employee, base_dates_set, start_date, end_date, filte def process_single_employee_shift_logs(logs, shift_details): """Mark Attendance for a set of logs belonging to a single shift. Assumtion: - 1. These logs belongs to an single shift, single employee and is not in a holiday shift. + 1. These logs belongs to an single shift, single employee and is not in a holiday date. 2. Logs are in chronological order """ + if shift_details.shift_type.enable_auto_attendance: + mark_attendance_and_link_log(logs, 'Skip', None) + return check_in_out_type = shift_details.shift_type.determine_check_in_and_check_out working_hours_calc_type = shift_details.shift_type.working_hours_calculation_based_on total_working_hours = calculate_working_hours(logs, check_in_out_type, working_hours_calc_type) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.json b/erpnext/hr/doctype/shift_assignment/shift_assignment.json index 437e4d7eef..d4cd1c4c93 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.json +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.json @@ -1,429 +1,132 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "HR-SHA-.YY.-.MM.-.#####", - "beta": 0, - "creation": "2018-04-13 16:25:04.562730", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_import": 1, + "autoname": "HR-SHA-.YY.-.MM.-.#####", + "creation": "2018-04-13 16:25:04.562730", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "department", + "shift_type", + "column_break_3", + "company", + "date", + "shift_request", + "amended_from" + ], "fields": [ { - "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": 0, - "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", + "label": "Employee", + "options": "Employee", + "reqd": 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 + }, { - "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 + }, { - "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 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "present", - "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": "Present", - "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_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "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": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 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": 0, - "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": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "date", - "fieldtype": "Date", - "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": "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": "shift_request", + "fieldtype": "Link", + "label": "Shift Request", + "options": "Shift Request", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shift_request", - "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": "Shift Request", - "length": 0, - "no_copy": 0, - "options": "Shift Request", - "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 - }, - { - "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 Assignment", - "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 Assignment", + "print_hide": 1, + "read_only": 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:41.155464", - "modified_by": "Administrator", - "module": "HR", - "name": "Shift Assignment", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "modified": "2019-05-30 15:40:54.418427", + "modified_by": "Administrator", + "module": "HR", + "name": "Shift Assignment", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "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": 0, - "write": 0 - }, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 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_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index c90894e6dd..ac8c8f2b89 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate -from erpnext.hr.doctype.holiday_list.holiday_list import get_holiday_list, is_holiday +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, is_holiday from datetime import timedelta, datetime class OverlapError(frappe.ValidationError): pass @@ -97,8 +97,8 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals if shift_type_name: holiday_list_name = frappe.db.get_value('Shift Type', shift_type_name, 'holiday_list') if not holiday_list_name: - holiday_list_name = get_holiday_list(employee) - if holiday_list_name and is_holiday(holiday_list_name, for_date): + holiday_list_name = get_holiday_list_for_employee(employee, False) + if holiday_list_name and is_holiday(holiday_list_name, for_date, False): shift_type_name = None if not shift_type_name and next_shift_direction: diff --git a/erpnext/hr/doctype/shift_type/shift_type.json b/erpnext/hr/doctype/shift_type/shift_type.json index 1ee30b2d5d..0c295c625e 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.json +++ b/erpnext/hr/doctype/shift_type/shift_type.json @@ -7,10 +7,10 @@ "field_order": [ "start_time", "end_time", - "disable_auto_attendance_for_this_shift", "column_break_3", "holiday_list", - "auto_attendance_configurations_section", + "enable_auto_attendance", + "auto_attendance_settings_section", "determine_check_in_and_check_out", "working_hours_calculation_based_on", "begin_check_in_before_shift_start_time", @@ -18,7 +18,7 @@ "column_break_10", "working_hours_threshold_for_half_day", "working_hours_threshold_for_absent", - "grace_period_configuration_auto_attendance_section", + "grace_period_settings_auto_attendance_section", "enable_entry_grace_period", "late_entry_grace_period", "consequence_after", @@ -86,33 +86,14 @@ "precision": "1" }, { - "depends_on": "eval:!doc.disable_auto_attendance_for_this_shift", - "fieldname": "auto_attendance_configurations_section", - "fieldtype": "Section Break", - "label": "Auto Attendance Configurations" - }, - { - "default": "45", + "default": "60", "description": "The time before the shift start time during which Employee Check-in is considered for attendance.", "fieldname": "begin_check_in_before_shift_start_time", "fieldtype": "Int", "label": "Begin check-in before shift start time (in minutes)" }, { - "default": "1", - "description": "Don't mark attendance based on Employee Attendance Log.", - "fieldname": "disable_auto_attendance_for_this_shift", - "fieldtype": "Check", - "label": "Disable Auto Attendance for this shift" - }, - { - "depends_on": "eval:!doc.disable_auto_attendance_for_this_shift", - "fieldname": "grace_period_configuration_auto_attendance_section", - "fieldtype": "Section Break", - "hidden": 1, - "label": "Grace Period Configuration For Auto Attendance" - }, - { + "default": "0", "fieldname": "enable_entry_grace_period", "fieldtype": "Check", "label": "Enable Entry Grace Period" @@ -144,11 +125,13 @@ "fieldtype": "Column Break" }, { + "default": "0", "fieldname": "enable_exit_grace_period", "fieldtype": "Check", "label": "Enable Exit Grace Period" }, { + "default": "0", "depends_on": "enable_exit_grace_period", "fieldname": "enable_different_consequence_for_early_exit", "fieldtype": "Check", @@ -177,13 +160,34 @@ "options": "Half Day\nAbsent" }, { - "description": "Time after the end of shift during which check-out is considered for attendance. (Zero to allow till next shift begins)", + "default": "60", + "description": "Time after the end of shift during which check-out is considered for attendance.", "fieldname": "allow_check_out_after_shift_end_time", "fieldtype": "Int", "label": "Allow check-out after shift end time (in minutes)" + }, + { + "depends_on": "enable_auto_attendance", + "fieldname": "auto_attendance_settings_section", + "fieldtype": "Section Break", + "label": "Auto Attendance Settings" + }, + { + "depends_on": "enable_auto_attendance", + "fieldname": "grace_period_settings_auto_attendance_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Grace Period Settings For Auto Attendance" + }, + { + "default": "0", + "description": "Mark attendance based on 'Employee Attendance Log' for Employees assigned to this shift.", + "fieldname": "enable_auto_attendance", + "fieldtype": "Check", + "label": "Enable Auto Attendance" } ], - "modified": "2019-05-16 18:57:00.150899", + "modified": "2019-05-30 15:31:35.594990", "modified_by": "Administrator", "module": "HR", "name": "Shift Type",