diff --git a/hrms/hr/doctype/shift_assignment/shift_assignment.py b/hrms/hr/doctype/shift_assignment/shift_assignment.py index f6b2d34f5f..5cdd5d7d9b 100644 --- a/hrms/hr/doctype/shift_assignment/shift_assignment.py +++ b/hrms/hr/doctype/shift_assignment/shift_assignment.py @@ -459,7 +459,11 @@ def get_prev_or_next_shift( for date_range in shift_dates: # midnight shifts will span more than a day start_date, end_date = date_range[0], add_days(date_range[1], 1) - reverse = next_shift_direction == "reverse" + + if reverse := (next_shift_direction == "reverse"): + end_date = min(end_date, for_timestamp.date()) + elif next_shift_direction == "forward": + start_date = max(start_date, for_timestamp.date()) for dt in generate_date_range(start_date, end_date, reverse=reverse): shift_details = get_employee_shift( diff --git a/hrms/hr/doctype/shift_assignment/test_shift_assignment.py b/hrms/hr/doctype/shift_assignment/test_shift_assignment.py index 2473c728dc..a33aba7abf 100644 --- a/hrms/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/hrms/hr/doctype/shift_assignment/test_shift_assignment.py @@ -295,3 +295,36 @@ def test_consecutive_day_and_night_shifts(self): self.assertEqual(checkin.shift_type, checkout.shift_type) self.assertEqual(checkin.actual_start.date(), today) self.assertEqual(checkout.actual_end.date(), today) + + def test_shift_details_on_consecutive_days_with_overlapping_timings(self): + # defaults + employee = make_employee("test_shift_assignment@example.com", company="_Test Company") + today = getdate() + yesterday = add_days(today, -1) + + # shift 1 + shift_type = setup_shift_type(shift_type="Morning", start_time="07:00:00", end_time="12:00:00") + make_shift_assignment(shift_type.name, employee, add_days(yesterday, -1), yesterday) + + # shift 2 + shift_type = setup_shift_type(shift_type="Afternoon", start_time="09:30:00", end_time="14:00:00") + make_shift_assignment(shift_type.name, employee, today, add_days(today, 1)) + + # current_shift shift log - checkin in the grace period of current shift, non-overlapping with prev shift + current_shift = get_actual_start_end_datetime_of_shift( + employee, get_datetime(f"{today} 14:01:00"), True + ) + self.assertEqual(current_shift.shift_type.name, "Afternoon") + self.assertEqual(current_shift.actual_start, get_datetime(f"{today} 08:30:00")) + self.assertEqual(current_shift.actual_end, get_datetime(f"{today} 15:00:00")) + + # previous shift + checkin = get_actual_start_end_datetime_of_shift( + employee, get_datetime(f"{yesterday} 07:01:00"), True + ) + checkout = get_actual_start_end_datetime_of_shift( + employee, get_datetime(f"{yesterday} 13:00:00"), True + ) + self.assertTrue(checkin.shift_type.name == checkout.shift_type.name == "Morning") + self.assertEqual(checkin.actual_start, get_datetime(f"{yesterday} 06:00:00")) + self.assertEqual(checkout.actual_end, get_datetime(f"{yesterday} 13:00:00")) diff --git a/hrms/hr/doctype/shift_type/test_shift_type.py b/hrms/hr/doctype/shift_type/test_shift_type.py index 1d3903c873..f961d5218a 100644 --- a/hrms/hr/doctype/shift_type/test_shift_type.py +++ b/hrms/hr/doctype/shift_type/test_shift_type.py @@ -371,6 +371,29 @@ def test_mark_absent_for_dates_with_no_attendance_for_midnight_shift(self): self.assertEqual(len(absent_records), 2) def test_do_not_mark_absent_before_shift_actual_end_time(self): + from hrms.hr.doctype.employee_checkin.test_employee_checkin import make_checkin + + employee = make_employee("test_employee_checkin@example.com", company="_Test Company") + today = getdate() + yesterday = add_days(today, -1) + + # shift 1 + shift_1 = setup_shift_type(shift_type="Morning", start_time="07:00:00", end_time="12:30:00") + make_shift_assignment(shift_1.name, employee, add_days(yesterday, -1), yesterday) + + # shift 2 + shift_2 = setup_shift_type(shift_type="Afternoon", start_time="09:30:00", end_time="18:00:00") + make_shift_assignment(shift_2.name, employee, today, add_days(today, 1)) + + # update last sync of checkin for shift 2 + shift_2.process_attendance_after = add_days(today, -2) + shift_2.last_sync_of_checkin = datetime.combine(today, get_time("09:01:00")) + shift_2.save() + shift_2.process_auto_attendance() + + self.assertIsNone(frappe.db.get_value("Attendance", {"attendance_date": today, "employee": employee})) + + def test_do_not_mark_absent_before_shift_actual_end_time_for_midnight_shift(self): """ Tests employee is not marked absent for a shift spanning 2 days before its actual end time