From 6fffdcf0c7a4e82136620508e2e405ce8b7a336a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 3 Apr 2022 22:37:10 +0530 Subject: [PATCH] test: Shift Type with Auto Attendance setup and working fix test setups --- .../employee_checkin/test_employee_checkin.py | 7 +- .../shift_assignment/test_shift_assignment.py | 18 +- .../hr/doctype/shift_type/test_shift_type.py | 291 ++++++++++++++++-- 3 files changed, 277 insertions(+), 39 deletions(-) diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index b0716be1b7cf..d9cf4bd13a44 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -23,8 +23,7 @@ mark_attendance_and_link_log, ) from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday -from erpnext.hr.doctype.shift_assignment.test_shift_assignment import make_shift_assignment -from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type +from erpnext.hr.doctype.shift_type.test_shift_type import make_shift_assignment, setup_shift_type from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list @@ -183,7 +182,9 @@ def test_fetch_shift_based_on_default_shift(self): def test_fetch_shift_spanning_over_two_days(self): employee = make_employee("test_employee_checkin@example.com", company="_Test Company") - shift_type = setup_shift_type(start_time="23:00:00", end_time="01:00:00") + shift_type = setup_shift_type( + shift_type="Midnight Shift", start_time="23:00:00", end_time="01:00:00" + ) date = getdate() next_day = add_days(date, 1) make_shift_assignment(shift_type.name, employee, date) diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 8759870a2066..048b573cd230 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -9,7 +9,7 @@ from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.shift_assignment.shift_assignment import OverlappingShiftError -from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type +from erpnext.hr.doctype.shift_type.test_shift_type import make_shift_assignment, setup_shift_type test_dependencies = ["Shift Type"] @@ -154,19 +154,3 @@ def test_multiple_shift_assignments_for_same_day(self): shift_type = setup_shift_type(shift_type="Shift 2", start_time="13:00:00", end_time="15:00:00") date = getdate() make_shift_assignment(shift_type.name, employee, date) - - -def make_shift_assignment(shift_type, employee, start_date, end_date=None): - shift_assignment = frappe.get_doc( - { - "doctype": "Shift Assignment", - "shift_type": shift_type, - "company": "_Test Company", - "employee": employee, - "start_date": start_date, - "end_date": end_date, - } - ).insert() - shift_assignment.submit() - - return shift_assignment diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py index b91a7c3d7ff6..13b890d2a9fc 100644 --- a/erpnext/hr/doctype/shift_type/test_shift_type.py +++ b/erpnext/hr/doctype/shift_type/test_shift_type.py @@ -2,35 +2,288 @@ # See license.txt import unittest +from datetime import datetime import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_days, get_time, get_year_ending, get_year_start, getdate, now_datetime +from erpnext.hr.doctype.employee.test_employee import make_employee -class TestShiftType(unittest.TestCase): - pass + +class TestShiftType(FrappeTestCase): + def setUp(self): + frappe.db.delete("Shift Type") + frappe.db.delete("Shift Assignment") + frappe.db.delete("Employee Checkin") + frappe.db.delete("Attendance") + + def test_mark_attendance(self): + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + + shift_type = setup_shift_type() + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("12:00:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", {"shift": shift_type.name}, ["status", "name"], as_dict=True + ) + self.assertEqual(attendance.status, "Present") + + def test_entry_and_exit_grace(self): + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + + # doesn't mark late entry until 60 mins after shift start i.e. till 9 + # doesn't mark late entry until 60 mins before shift end i.e. 11 + shift_type = setup_shift_type( + enable_entry_grace_period=1, + enable_exit_grace_period=1, + late_entry_grace_period=60, + early_exit_grace_period=60, + ) + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("09:30:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("10:30:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", + {"shift": shift_type.name}, + ["status", "name", "late_entry", "early_exit"], + as_dict=True, + ) + self.assertEqual(attendance.status, "Present") + self.assertEqual(attendance.late_entry, 1) + self.assertEqual(attendance.early_exit, 1) + + def test_working_hours_threshold_for_half_day(self): + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + shift_type = setup_shift_type(shift_type="Half Day Test", working_hours_threshold_for_half_day=2) + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("09:30:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", {"shift": shift_type.name}, ["status", "working_hours"], as_dict=True + ) + self.assertEqual(attendance.status, "Half Day") + self.assertEqual(attendance.working_hours, 1.5) + + def test_working_hours_threshold_for_absent(self): + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + shift_type = setup_shift_type(shift_type="Absent Test", working_hours_threshold_for_absent=2) + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("09:30:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", {"shift": shift_type.name}, ["status", "working_hours"], as_dict=True + ) + self.assertEqual(attendance.status, "Absent") + self.assertEqual(attendance.working_hours, 1.5) + + def test_working_hours_threshold_for_absent_and_half_day_1(self): + # considers half day over absent + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + shift_type = setup_shift_type( + shift_type="Half Day + Absent Test", + working_hours_threshold_for_half_day=1, + working_hours_threshold_for_absent=2, + ) + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("08:45:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", {"shift": shift_type.name}, ["status", "working_hours"], as_dict=True + ) + self.assertEqual(attendance.status, "Half Day") + self.assertEqual(attendance.working_hours, 0.75) + + def test_working_hours_threshold_for_absent_and_half_day_2(self): + # considers absent over half day + from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + shift_type = setup_shift_type( + shift_type="Half Day + Absent Test", + working_hours_threshold_for_half_day=1, + working_hours_threshold_for_absent=2, + ) + date = getdate() + make_shift_assignment(shift_type.name, employee, date) + + timestamp = datetime.combine(date, get_time("08:00:00")) + log_in = make_checkin(employee, timestamp) + self.assertEqual(log_in.shift, shift_type.name) + + timestamp = datetime.combine(date, get_time("09:30:00")) + log_out = make_checkin(employee, timestamp) + self.assertEqual(log_out.shift, shift_type.name) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value("Attendance", {"shift": shift_type.name}, "status") + self.assertEqual(attendance, "Absent") + + def test_mark_absent_for_dates_with_no_attendance(self): + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + shift_type = setup_shift_type(shift_type="Test Absent with no Attendance") + + # absentees are auto-marked one day after to wait for any manual attendance records + date = add_days(getdate(), -1) + make_shift_assignment(shift_type.name, employee, date) + + shift_type.process_auto_attendance() + + attendance = frappe.db.get_value( + "Attendance", {"attendance_date": date, "employee": employee}, "status" + ) + self.assertEqual(attendance, "Absent") + + def test_get_start_and_end_dates(self): + date = getdate() + + doj = add_days(date, -30) + relieving_date = add_days(date, -5) + employee = make_employee( + "test_employee_dates@example.com", + company="_Test Company", + date_of_joining=doj, + relieving_date=relieving_date, + ) + shift_type = setup_shift_type( + shift_type="Test Absent with no Attendance", process_attendance_after=add_days(doj, 2) + ) + + make_shift_assignment(shift_type.name, employee, add_days(date, -25)) + + shift_type.process_auto_attendance() + + # should not mark absent before shift assignment/process attendance after date + attendance = frappe.db.get_value( + "Attendance", {"attendance_date": doj, "employee": employee}, "name" + ) + self.assertIsNone(attendance) + + # mark absent on Relieving Date + attendance = frappe.db.get_value( + "Attendance", {"attendance_date": relieving_date, "employee": employee}, "status" + ) + self.assertEquals(attendance, "Absent") + + # should not mark absent after Relieving Date + attendance = frappe.db.get_value( + "Attendance", {"attendance_date": add_days(relieving_date, 1), "employee": employee}, "name" + ) + self.assertIsNone(attendance) def setup_shift_type(**args): args = frappe._dict(args) - shift_type = frappe.new_doc("Shift Type") - shift_type.__newname = args.shift_type or "_Test Shift" - shift_type.start_time = args.start_time or "08:00:00" - shift_type.end_time = args.end_time or "12:00:00" - shift_type.holiday_list = args.holiday_list - shift_type.enable_auto_attendance = 1 - - shift_type.determine_check_in_and_check_out = ( - args.determine_check_in_and_check_out - or "Alternating entries as IN and OUT during the same shift" - ) - shift_type.working_hours_calculation_based_on = ( - args.working_hours_calculation_based_on or "First Check-in and Last Check-out" - ) - shift_type.begin_check_in_before_shift_start_time = ( - args.begin_check_in_before_shift_start_time or 60 + date = getdate() + + shift_type = frappe.get_doc( + { + "doctype": "Shift Type", + "__newname": args.shift_type or "_Test Shift", + "start_time": "08:00:00", + "end_time": "12:00:00", + "enable_auto_attendance": 1, + "determine_check_in_and_check_out": "Alternating entries as IN and OUT during the same shift", + "working_hours_calculation_based_on": "First Check-in and Last Check-out", + "begin_check_in_before_shift_start_time": 60, + "allow_check_out_after_shift_end_time": 60, + "process_attendance_after": add_days(date, -2), + "last_sync_of_checkin": now_datetime(), + } ) - shift_type.allow_check_out_after_shift_end_time = args.allow_check_out_after_shift_end_time or 60 + holiday_list = "Employee Checkin Test Holiday List" + if not frappe.db.exists("Holiday List", "Employee Checkin Test Holiday List"): + holiday_list = frappe.get_doc( + { + "doctype": "Holiday List", + "holiday_list_name": "Employee Checkin Test Holiday List", + "from_date": get_year_start(date), + "to_date": get_year_ending(date), + } + ).insert() + holiday_list = holiday_list.name + + shift_type.holiday_list = holiday_list + shift_type.update(args) shift_type.save() return shift_type + + +def make_shift_assignment(shift_type, employee, start_date, end_date=None): + shift_assignment = frappe.get_doc( + { + "doctype": "Shift Assignment", + "shift_type": shift_type, + "company": "_Test Company", + "employee": employee, + "start_date": start_date, + "end_date": end_date, + } + ).insert() + shift_assignment.submit() + + return shift_assignment