def test_skip_auto_attendance_for_duplicate_record(self): # Skip auto attendance in case of duplicate attendance record from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin employee = make_employee("*****@*****.**", company="_Test Company") shift_type = setup_shift_type() date = getdate() # mark attendance mark_attendance(employee, date, "Present") 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) # auto attendance should skip marking shift_type.process_auto_attendance() log_in.reload() log_out.reload() self.assertEqual(log_in.skip_auto_attendance, 1) self.assertEqual(log_out.skip_auto_attendance, 1)
def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. The Absent is marked starting from 'process_attendance_after' or employee creation date. """ date_of_joining, relieving_date, employee_creation = frappe.db.get_value( "Employee", employee, ["date_of_joining", "relieving_date", "creation"]) if not date_of_joining: date_of_joining = employee_creation.date() start_date = max(getdate(self.process_attendance_after), date_of_joining) actual_shift_datetime = get_actual_start_end_datetime_of_shift( employee, get_datetime(self.last_sync_of_checkin), True) last_shift_time = actual_shift_datetime[0] if actual_shift_datetime[ 0] else get_datetime(self.last_sync_of_checkin) prev_shift = get_employee_shift( employee, last_shift_time.date() - timedelta(days=1), True, 'reverse') if prev_shift: end_date = min( prev_shift.start_datetime.date(), relieving_date ) if relieving_date else prev_shift.start_datetime.date() else: return holiday_list_name = self.holiday_list if not holiday_list_name: holiday_list_name = get_holiday_list_for_employee(employee, False) dates = get_filtered_date_list(employee, start_date, end_date, holiday_list=holiday_list_name) for date in dates: shift_details = get_employee_shift(employee, date, True) if shift_details and shift_details.shift_type.name == self.name: mark_attendance(employee, date, self.name, 'Absent')
def test_skip_auto_attendance_for_overlapping_shift(self): # Skip auto attendance in case of overlapping shift attendance record # this case won't occur in case of shift assignment, since it will not allow overlapping shifts to be assigned # can happen if manual attendance records are created from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.employee_checkin.test_employee_checkin import make_checkin employee = make_employee("*****@*****.**", company="_Test Company") shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00") shift_2 = setup_shift_type(shift_type="Shift 2", start_time="09:30:00", end_time="11:00:00") date = getdate() # mark attendance mark_attendance(employee, date, "Present", shift=shift_1.name) make_shift_assignment(shift_2.name, employee, date) timestamp = datetime.combine(date, get_time("09:30:00")) log_in = make_checkin(employee, timestamp) self.assertEqual(log_in.shift, shift_2.name) timestamp = datetime.combine(date, get_time("11:00:00")) log_out = make_checkin(employee, timestamp) self.assertEqual(log_out.shift, shift_2.name) # auto attendance should be skipped for shift 2 # since it is already marked for overlapping shift 1 shift_2.process_auto_attendance() log_in.reload() log_out.reload() self.assertEqual(log_in.skip_auto_attendance, 1) self.assertEqual(log_out.skip_auto_attendance, 1)
def test_unmarked_days_excluding_holidays(self): now = now_datetime() previous_month = now.month - 1 first_day = now.replace(day=1).replace(month=previous_month).date() employee = make_employee( "*****@*****.**", date_of_joining=add_days(first_day, -1) ) frappe.db.delete("Attendance", {"employee": employee}) frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list) first_sunday = get_first_sunday(self.holiday_list, for_date=first_day) mark_attendance(employee, first_day, "Present") month_name = get_month_name(first_day) unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True) unmarked_days = [getdate(date) for date in unmarked_days] # attendance already marked for the day self.assertNotIn(first_day, unmarked_days) # attendance unmarked self.assertIn(getdate(add_days(first_day, 1)), unmarked_days) # holidays not considered in unmarked days self.assertNotIn(first_sunday, unmarked_days)
def test_unmarked_days_as_per_joining_and_relieving_dates(self): now = now_datetime() previous_month = now.month - 1 first_day = now.replace(day=1).replace(month=previous_month).date() doj = add_days(first_day, 1) relieving_date = add_days(first_day, 5) employee = make_employee( "*****@*****.**", date_of_joining=doj, relieving_date=relieving_date ) frappe.db.set_value("Employee", employee, "holiday_list", self.holiday_list) attendance_date = add_days(first_day, 2) mark_attendance(employee, attendance_date, "Present") month_name = get_month_name(first_day) unmarked_days = get_unmarked_days(employee, month_name) unmarked_days = [getdate(date) for date in unmarked_days] # attendance already marked for the day self.assertNotIn(attendance_date, unmarked_days) # date before doj not in unmarked days self.assertNotIn(add_days(doj, -1), unmarked_days) # date after relieving not in unmarked days self.assertNotIn(add_days(relieving_date, 1), unmarked_days)
def test_duplicate_attendance_with_shift(self): from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type employee = make_employee("*****@*****.**", company="_Test Company") date = nowdate() shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00") mark_attendance(employee, date, "Present", shift=shift_1.name) # attendance record with shift attendance = frappe.get_doc( { "doctype": "Attendance", "employee": employee, "attendance_date": date, "status": "Absent", "company": "_Test Company", "shift": shift_1.name, } ) self.assertRaises(DuplicateAttendanceError, attendance.insert) # attendance record without any shift attendance = frappe.get_doc( { "doctype": "Attendance", "employee": employee, "attendance_date": date, "status": "Absent", "company": "_Test Company", } ) self.assertRaises(DuplicateAttendanceError, attendance.insert)
def test_duplicate_attendance(self): employee = make_employee("*****@*****.**", company="_Test Company") date = nowdate() mark_attendance(employee, date, "Present") attendance = frappe.get_doc( { "doctype": "Attendance", "employee": employee, "attendance_date": date, "status": "Absent", "company": "_Test Company", } ) self.assertRaises(DuplicateAttendanceError, attendance.insert)
def test_mark_absent(self): from erpnext.hr.doctype.employee.test_employee import make_employee employee = make_employee("*****@*****.**") date = nowdate() frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date}) from erpnext.hr.doctype.attendance.attendance import mark_attendance attendance = mark_attendance(employee, date, 'Absent') fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'}) self.assertEqual(attendance, fetch_attendance)
def test_mark_absent(self): employee = make_employee("*****@*****.**") date = nowdate() attendance = mark_attendance(employee, date, "Absent") fetch_attendance = frappe.get_value( "Attendance", {"employee": employee, "attendance_date": date, "status": "Absent"} ) self.assertEqual(attendance, fetch_attendance)
def test_allow_attendance_with_different_shifts(self): # allows attendance with 2 different non-overlapping shifts from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type employee = make_employee("*****@*****.**", company="_Test Company") date = nowdate() shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00") shift_2 = setup_shift_type(shift_type="Shift 2", start_time="11:00:00", end_time="12:00:00") mark_attendance(employee, date, "Present", shift_1.name) frappe.get_doc( { "doctype": "Attendance", "employee": employee, "attendance_date": date, "status": "Absent", "company": "_Test Company", "shift": shift_2.name, } ).insert()
def test_monthly_attendance_sheet_report(self): now = now_datetime() previous_month = now.month - 1 previous_month_first = now.replace(day=1).replace(month=previous_month).date() company = frappe.db.get_value("Employee", self.employee, "company") # mark different attendance status on first 3 days of previous month mark_attendance(self.employee, previous_month_first, "Absent") mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present") mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave") filters = frappe._dict( { "month": previous_month, "year": now.year, "company": company, } ) report = execute(filters=filters) employees = report[1][0] datasets = report[3]["data"]["datasets"] absent = datasets[0]["values"] present = datasets[1]["values"] leaves = datasets[2]["values"] # ensure correct attendance is reflect on the report self.assertIn(self.employee, employees) self.assertEqual(absent[0], 1) self.assertEqual(present[1], 1) self.assertEqual(leaves[2], 1)
def test_attendance_with_group_by_filter(self): now = now_datetime() previous_month = now.month - 1 previous_month_first = now.replace(day=1).replace( month=previous_month).date() company = frappe.db.get_value("Employee", self.employee, "company") # attendance with shift mark_attendance(self.employee, previous_month_first, "Absent", "Day Shift") mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present", "Day Shift") # attendance without shift mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "On Leave") mark_attendance(self.employee, previous_month_first + relativedelta(days=3), "Present") filters = frappe._dict({ "month": previous_month, "year": now.year, "company": company, "group_by": "Department" }) report = execute(filters=filters) department = frappe.db.get_value("Employee", self.employee, "department") department_row = report[1][0] self.assertIn(department, department_row["department"]) day_shift_row = report[1][1] row_without_shift = report[1][2] self.assertEqual(day_shift_row["shift"], "Day Shift") self.assertEqual(day_shift_row[1], "A") # absent on the 1st day of the month self.assertEqual(day_shift_row[2], "P") # present on the 2nd day self.assertEqual(row_without_shift["shift"], None) self.assertEqual(row_without_shift[3], "L") # on leave on the 3rd day self.assertEqual(row_without_shift[4], "P") # present on the 4th day
def test_overlapping_shift_attendance_validation(self): from erpnext.hr.doctype.shift_type.test_shift_type import setup_shift_type employee = make_employee("*****@*****.**", company="_Test Company") date = nowdate() shift_1 = setup_shift_type(shift_type="Shift 1", start_time="08:00:00", end_time="10:00:00") shift_2 = setup_shift_type(shift_type="Shift 2", start_time="09:30:00", end_time="11:00:00") mark_attendance(employee, date, "Present", shift=shift_1.name) # attendance record with overlapping shift attendance = frappe.get_doc( { "doctype": "Attendance", "employee": employee, "attendance_date": date, "status": "Absent", "company": "_Test Company", "shift": shift_2.name, } ) self.assertRaises(OverlappingShiftAttendanceError, attendance.insert)
def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. The Absent is marked starting from 'process_attendance_after' or employee creation date. """ date_of_joining, relieving_date, employee_creation = frappe.db.get_value( "Employee", employee, ["date_of_joining", "relieving_date", "creation"] ) if not date_of_joining: date_of_joining = employee_creation.date() start_date = max(getdate(self.process_attendance_after), date_of_joining) actual_shift_datetime = get_actual_start_end_datetime_of_shift( employee, get_datetime(self.last_sync_of_checkin), True ) last_shift_time = ( actual_shift_datetime[0] if actual_shift_datetime[0] else get_datetime(self.last_sync_of_checkin) ) prev_shift = get_employee_shift( employee, last_shift_time.date() - timedelta(days=1), True, "reverse" ) if prev_shift: end_date = ( min(prev_shift.start_datetime.date(), relieving_date) if relieving_date else prev_shift.start_datetime.date() ) else: return holiday_list_name = self.holiday_list if not holiday_list_name: holiday_list_name = get_holiday_list_for_employee(employee, False) dates = get_filtered_date_list(employee, start_date, end_date, holiday_list=holiday_list_name) for date in dates: shift_details = get_employee_shift(employee, date, True) if shift_details and shift_details.shift_type.name == self.name: attendance = mark_attendance(employee, date, "Absent", self.name) if attendance: frappe.get_doc( { "doctype": "Comment", "comment_type": "Comment", "reference_doctype": "Attendance", "reference_name": attendance, "content": frappe._("Employee was marked Absent due to missing Employee Checkins."), } ).insert(ignore_permissions=True)
def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. The Absent is marked starting from 'process_attendance_after' or employee creation date. """ start_date, end_date = self.get_start_and_end_dates(employee) # no shift assignment found, no need to process absent attendance records if start_date is None: return holiday_list_name = self.holiday_list if not holiday_list_name: holiday_list_name = get_holiday_list_for_employee(employee, False) start_time = get_time(self.start_time) for date in daterange(getdate(start_date), getdate(end_date)): if is_holiday(holiday_list_name, date): # skip marking absent on a holiday continue timestamp = datetime.combine(date, start_time) shift_details = get_employee_shift(employee, timestamp, True) if shift_details and shift_details.shift_type.name == self.name: attendance = mark_attendance(employee, date, "Absent", self.name) if attendance: frappe.get_doc({ "doctype": "Comment", "comment_type": "Comment", "reference_doctype": "Attendance", "reference_name": attendance, "content": frappe. _("Employee was marked Absent due to missing Employee Checkins." ), }).insert(ignore_permissions=True)
def test_payment_days_based_on_attendance(self): from erpnext.hr.doctype.attendance.attendance import mark_attendance no_of_days = self.get_no_of_days() # Payroll based on attendance frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75) emp_id = make_employee("*****@*****.**") frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"}) frappe.db.set_value("Leave Type", "Leave Without Pay", "include_holiday", 0) month_start_date = get_first_day(nowdate()) month_end_date = get_last_day(nowdate()) first_sunday = frappe.db.sql(""" select holiday_date from `tabHoliday` where parent = 'Salary Slip Test Holiday List' and holiday_date between %s and %s order by holiday_date """, (month_start_date, month_end_date))[0][0] mark_attendance(emp_id, first_sunday, 'Absent', ignore_validate=True) # invalid lwp mark_attendance(emp_id, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent mark_attendance(emp_id, add_days(first_sunday, 2), 'Half Day', leave_type='Leave Without Pay', ignore_validate=True) # valid 0.75 lwp mark_attendance(emp_id, add_days(first_sunday, 3), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # valid lwp mark_attendance(emp_id, add_days(first_sunday, 4), 'On Leave', leave_type='Casual Leave', ignore_validate=True) # invalid lwp mark_attendance(emp_id, add_days(first_sunday, 7), 'On Leave', leave_type='Leave Without Pay', ignore_validate=True) # invalid lwp ss = make_employee_salary_slip("*****@*****.**", "Monthly") self.assertEqual(ss.leave_without_pay, 1.25) self.assertEqual(ss.absent_days, 1) days_in_month = no_of_days[0] no_of_holidays = no_of_days[1] self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 2.25) #Gross pay calculation based on attendances gross_pay = 78000 - ((78000 / (days_in_month - no_of_holidays)) * flt(ss.leave_without_pay + ss.absent_days)) self.assertEqual(ss.gross_pay, gross_pay) frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
def test_monthly_attendance_sheet_with_summarized_view(self): now = now_datetime() previous_month = now.month - 1 previous_month_first = now.replace(day=1).replace( month=previous_month).date() company = frappe.db.get_value("Employee", self.employee, "company") # attendance with shift mark_attendance(self.employee, previous_month_first, "Absent", "Day Shift") mark_attendance(self.employee, previous_month_first + relativedelta(days=1), "Present", "Day Shift") mark_attendance(self.employee, previous_month_first + relativedelta(days=2), "Half Day") # half day mark_attendance(self.employee, previous_month_first + relativedelta(days=3), "Present") # attendance without shift mark_attendance(self.employee, previous_month_first + relativedelta(days=4), "Present", late_entry=1) # late entry mark_attendance(self.employee, previous_month_first + relativedelta(days=5), "Present", early_exit=1) # early exit leave_application = get_leave_application(self.employee) filters = frappe._dict({ "month": previous_month, "year": now.year, "company": company, "summarized_view": 1 }) report = execute(filters=filters) row = report[1][0] self.assertEqual(row["employee"], self.employee) # 4 present + half day absent 0.5 self.assertEqual(row["total_present"], 4.5) # 1 present + half day absent 0.5 self.assertEqual(row["total_absent"], 1.5) # leave days + half day leave 0.5 self.assertEqual(row["total_leaves"], leave_application.total_leave_days + 0.5) self.assertEqual(row["_test_leave_type"], leave_application.total_leave_days) self.assertEqual(row["total_late_entries"], 1) self.assertEqual(row["total_early_exits"], 1)