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
This commit is contained in:
karthikeyan5 2019-05-30 16:12:34 +05:30
parent 23cedfd6c8
commit 66e459b35d
11 changed files with 202 additions and 517 deletions

View File

@ -4,8 +4,17 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
from frappe.utils import nowdate
test_records = frappe.get_test_records('Attendance') test_records = frappe.get_test_records('Attendance')
class TestAttendance(unittest.TestCase): 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)

View File

@ -16,7 +16,7 @@
"middle_name", "middle_name",
"last_name", "last_name",
"employee_name", "employee_name",
"biometric_rf_id", "attendance_device_id",
"image", "image",
"column_break1", "column_break1",
"company", "company",
@ -511,6 +511,7 @@
"options": "Email" "options": "Email"
}, },
{ {
"default": "0",
"fieldname": "unsubscribed", "fieldname": "unsubscribed",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Unsubscribed" "label": "Unsubscribed"
@ -749,9 +750,9 @@
"label": "Old Parent" "label": "Old Parent"
}, },
{ {
"fieldname": "biometric_rf_id", "fieldname": "attendance_device_id",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Biometric/RF tag ID ", "label": "Attendance Device ID (Biometric/RF tag ID)",
"no_copy": 1, "no_copy": 1,
"unique": 1 "unique": 1
} }
@ -759,7 +760,7 @@
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 24, "idx": 24,
"image_field": "image", "image_field": "image",
"modified": "2019-05-08 14:32:06.443825", "modified": "2019-05-29 17:33:11.988538",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",

View File

@ -319,12 +319,12 @@ def get_holiday_list_for_employee(employee, raise_exception=True):
return holiday_list 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 '''Returns True if given Employee has an holiday on the given date
:param employee: Employee `name` :param employee: Employee `name`
:param date: Date to check. Will check for today if None''' :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: if not date:
date = today() date = today()

View File

@ -15,23 +15,24 @@ class EmployeeAttendanceLog(Document):
@frappe.whitelist() @frappe.whitelist()
def add_log_based_on_biometric_rf_id(biometric_rf_id, timestamp, device_id=None, log_type=None): 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 biometric_rf_id and creates a Employee Attendance Log. """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 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 device_id: (optional)Location / Device ID. A short string is expected.
:param log_type: (optional)Direction of the Punch if available (IN/OUT). :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: if not employee_field_value or not timestamp:
frappe.throw(_("'biometric_rf_id' and 'timestamp' are required.")) 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: if employee:
employee = employee[0] employee = employee[0]
else: 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 = frappe.new_doc("Employee Attendance Log")
doc.employee = employee.name 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.device_id = device_id
doc.log_type = log_type doc.log_type = log_type
doc.insert() doc.insert()
frappe.db.commit()
return doc return doc

View File

@ -8,18 +8,18 @@ from frappe.utils import now_datetime, nowdate, to_timedelta
import unittest import unittest
from datetime import timedelta 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 from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeAttendanceLog(unittest.TestCase): class TestEmployeeAttendanceLog(unittest.TestCase):
def test_add_log_based_on_biometric_rf_id(self): def test_add_log_based_on_employee_field(self):
employee = make_employee("test_add_log_based_on_biometric_rf_id@example.com") employee = make_employee("test_add_log_based_on_employee_field@example.com")
employee = frappe.get_doc("Employee", employee) employee = frappe.get_doc("Employee", employee)
employee.biometric_rf_id = '3344' employee.attendance_device_id = '3344'
employee.save() employee.save()
time_now = now_datetime().__str__()[:-7] 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.employee, employee.name)
self.assertEqual(employee_attendance_log.time, time_now) self.assertEqual(employee_attendance_log.time, time_now)
self.assertEqual(employee_attendance_log.device_id, 'mumbai_first_floor') self.assertEqual(employee_attendance_log.device_id, 'mumbai_first_floor')

View File

@ -84,26 +84,3 @@ def get_events(start, end, filters=None):
fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'], fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'],
filters = filters, filters = filters,
update={"allDay": 1}) 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)

View File

@ -6,39 +6,27 @@ import frappe
import unittest import unittest
from frappe.utils import getdate from frappe.utils import getdate
from datetime import timedelta from datetime import timedelta
from erpnext.hr.doctype.employee.test_employee import make_employee
class TestHolidayList(unittest.TestCase): class TestHolidayList(unittest.TestCase):
def test_get_holiday_list(self): def test_holiday_list(self):
holiday_list = make_holiday_list("test_get_holiday_list") today_date = getdate()
employee = make_employee("test_get_holiday_list@example.com") test_holiday_dates = [today_date-timedelta(days=5), today_date-timedelta(days=4)]
employee = frappe.get_doc("Employee", employee) holiday_list = make_holiday_list("test_is_holiday",
employee.holiday_list = None holiday_dates=[
employee.save() {'holiday_date': test_holiday_dates[0], 'description': 'test holiday'},
company = frappe.get_doc("Company", employee.company) {'holiday_date': test_holiday_dates[1], 'description': 'test holiday2'}
company_default_holiday_list = company.default_holiday_list ])
fetched_holiday_list = frappe.get_value('Holiday List', holiday_list.name)
from erpnext.hr.doctype.holiday_list.holiday_list import get_holiday_list self.assertEqual(holiday_list.name, fetched_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 make_holiday_list(name, from_date=getdate()-timedelta(days=10), to_date=getdate(), holiday_dates=None): 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): frappe.delete_doc_if_exists("Holiday List", name, force=1)
doc = frappe.get_doc({ doc = frappe.get_doc({
"doctype": "Holiday List", "doctype": "Holiday List",
"holiday_list_name": name, "holiday_list_name": name,
"from_date" : from_date, "from_date" : from_date,
"to_date" : to_date "to_date" : to_date,
"holidays" : holiday_dates
}).insert() }).insert()
doc.holidays = holiday_dates
doc.save()
else:
doc = frappe.get_doc("Holiday List", name)
return doc return doc

View File

@ -12,7 +12,7 @@ from frappe import _
from frappe.model.document import Document 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.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.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 from erpnext.hr.doctype.attendance.attendance import mark_absent
class HRSettings(Document): 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: while logs and logs[0].time <= actual_shift_end:
single_shift_logs.append(logs.pop(0)) single_shift_logs.append(logs.pop(0))
process_single_employee_shift_logs(single_shift_logs, shift_details) 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. """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 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. 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: if prev_shift:
end_date = prev_shift.start_datetime.date() 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': 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) mark_absent(employee, date)
return return
else: 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): if get_employee_shift(employee, date, consider_default_shift):
mark_absent(employee, date) mark_absent(employee, date)
elif hr_settings.attendance_for_employee_without_shift == 'At least one Employee Attendance Log per day as present': 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) mark_absent(employee, date)
else: else:
for date in get_filtered_date_list(employee, "Assigned Shifts", start_date, end_date): 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): def process_single_employee_shift_logs(logs, shift_details):
"""Mark Attendance for a set of logs belonging to a single shift. """Mark Attendance for a set of logs belonging to a single shift.
Assumtion: 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 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 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 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) total_working_hours = calculate_working_hours(logs, check_in_out_type, working_hours_calc_type)

View File

@ -1,381 +1,101 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"autoname": "HR-SHA-.YY.-.MM.-.#####", "autoname": "HR-SHA-.YY.-.MM.-.#####",
"beta": 0,
"creation": "2018-04-13 16:25:04.562730", "creation": "2018-04-13 16:25:04.562730",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"employee",
"employee_name",
"department",
"shift_type",
"column_break_3",
"company",
"date",
"shift_request",
"amended_from"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee", "fieldname": "employee",
"fieldtype": "Link", "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", "label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee", "options": "Employee",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
"fieldname": "employee_name", "fieldname": "employee_name",
"fieldtype": "Data", "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", "label": "Employee Name",
"length": 0, "read_only": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department", "fetch_from": "employee.department",
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "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", "label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department", "options": "Department",
"permlevel": 0, "read_only": 1
"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": "shift_type", "fieldname": "shift_type",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Shift Type", "label": "Shift Type",
"length": 0,
"no_copy": 0,
"options": "Shift Type", "options": "Shift Type",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "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", "label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company", "options": "Company",
"permlevel": 0, "reqd": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "date", "fieldname": "date",
"fieldtype": "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_list_view": 1,
"in_standard_filter": 0, "label": "Date"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "shift_request", "fieldname": "shift_request",
"fieldtype": "Link", "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", "label": "Shift Request",
"length": 0,
"no_copy": 0,
"options": "Shift Request", "options": "Shift Request",
"permlevel": 0, "read_only": 1
"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", "fieldname": "amended_from",
"fieldtype": "Link", "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", "label": "Amended From",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Shift Assignment", "options": "Shift Assignment",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "modified": "2019-05-30 15:40:54.418427",
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:41.155464",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Shift Assignment", "name": "Shift Assignment",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Employee", "role": "Employee",
"set_user_permissions": 0, "share": 1
"share": 1,
"submit": 0,
"write": 0
}, },
{ {
"amend": 1, "amend": 1,
@ -384,46 +104,29 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "employee_name", "title_field": "employee_name",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@ -7,7 +7,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate 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 from datetime import timedelta, datetime
class OverlapError(frappe.ValidationError): pass 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: if shift_type_name:
holiday_list_name = frappe.db.get_value('Shift Type', shift_type_name, 'holiday_list') holiday_list_name = frappe.db.get_value('Shift Type', shift_type_name, 'holiday_list')
if not holiday_list_name: if not holiday_list_name:
holiday_list_name = get_holiday_list(employee) holiday_list_name = get_holiday_list_for_employee(employee, False)
if holiday_list_name and is_holiday(holiday_list_name, for_date): if holiday_list_name and is_holiday(holiday_list_name, for_date, False):
shift_type_name = None shift_type_name = None
if not shift_type_name and next_shift_direction: if not shift_type_name and next_shift_direction:

View File

@ -7,10 +7,10 @@
"field_order": [ "field_order": [
"start_time", "start_time",
"end_time", "end_time",
"disable_auto_attendance_for_this_shift",
"column_break_3", "column_break_3",
"holiday_list", "holiday_list",
"auto_attendance_configurations_section", "enable_auto_attendance",
"auto_attendance_settings_section",
"determine_check_in_and_check_out", "determine_check_in_and_check_out",
"working_hours_calculation_based_on", "working_hours_calculation_based_on",
"begin_check_in_before_shift_start_time", "begin_check_in_before_shift_start_time",
@ -18,7 +18,7 @@
"column_break_10", "column_break_10",
"working_hours_threshold_for_half_day", "working_hours_threshold_for_half_day",
"working_hours_threshold_for_absent", "working_hours_threshold_for_absent",
"grace_period_configuration_auto_attendance_section", "grace_period_settings_auto_attendance_section",
"enable_entry_grace_period", "enable_entry_grace_period",
"late_entry_grace_period", "late_entry_grace_period",
"consequence_after", "consequence_after",
@ -86,33 +86,14 @@
"precision": "1" "precision": "1"
}, },
{ {
"depends_on": "eval:!doc.disable_auto_attendance_for_this_shift", "default": "60",
"fieldname": "auto_attendance_configurations_section",
"fieldtype": "Section Break",
"label": "Auto Attendance Configurations"
},
{
"default": "45",
"description": "The time before the shift start time during which Employee Check-in is considered for attendance.", "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", "fieldname": "begin_check_in_before_shift_start_time",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Begin check-in before shift start time (in minutes)" "label": "Begin check-in before shift start time (in minutes)"
}, },
{ {
"default": "1", "default": "0",
"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"
},
{
"fieldname": "enable_entry_grace_period", "fieldname": "enable_entry_grace_period",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Entry Grace Period" "label": "Enable Entry Grace Period"
@ -144,11 +125,13 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"default": "0",
"fieldname": "enable_exit_grace_period", "fieldname": "enable_exit_grace_period",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Enable Exit Grace Period" "label": "Enable Exit Grace Period"
}, },
{ {
"default": "0",
"depends_on": "enable_exit_grace_period", "depends_on": "enable_exit_grace_period",
"fieldname": "enable_different_consequence_for_early_exit", "fieldname": "enable_different_consequence_for_early_exit",
"fieldtype": "Check", "fieldtype": "Check",
@ -177,13 +160,34 @@
"options": "Half Day\nAbsent" "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", "fieldname": "allow_check_out_after_shift_end_time",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Allow check-out after shift end time (in minutes)" "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", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Shift Type", "name": "Shift Type",