feat: Consider Holiday List in Student Leave Application and Attendance (#23388)
* feat: Consider holiday list in Student Attendance and Leave Application * feat: Show Holidays as 'H' in Student Monthly Attendance Sheet * fix: check if date is a holiday in attendance reports * test: skip attendance record creation for holidays * fix: holiday list validation * fix: clean up after test * fix: codacy * fix: show date in user format * fix: remove ununsed imports * fix: sider * fix: test Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
		
							parent
							
								
									7915a3acae
								
							
						
					
					
						commit
						30d58cc3d7
					
				| @ -6,8 +6,10 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.model.document import Document | ||||
| from frappe import _ | ||||
| from frappe.utils import get_link_to_form, getdate | ||||
| from frappe.utils import get_link_to_form, getdate, formatdate | ||||
| from erpnext import get_default_company | ||||
| from erpnext.education.api import get_student_group_students | ||||
| from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday | ||||
| 
 | ||||
| class StudentAttendance(Document): | ||||
| 	def validate(self): | ||||
| @ -17,6 +19,7 @@ class StudentAttendance(Document): | ||||
| 		self.set_student_group() | ||||
| 		self.validate_student() | ||||
| 		self.validate_duplication() | ||||
| 		self.validate_is_holiday() | ||||
| 
 | ||||
| 	def set_date(self): | ||||
| 		if self.course_schedule: | ||||
| @ -78,3 +81,18 @@ class StudentAttendance(Document): | ||||
| 			record = get_link_to_form('Student Attendance', attendance_record) | ||||
| 			frappe.throw(_('Student Attendance record {0} already exists against the Student {1}') | ||||
| 				.format(record, frappe.bold(self.student)), title=_('Duplicate Entry')) | ||||
| 
 | ||||
| 	def validate_is_holiday(self): | ||||
| 		holiday_list = get_holiday_list() | ||||
| 		if is_holiday(holiday_list, self.date): | ||||
| 			frappe.throw(_('Attendance cannot be marked for {0} as it is a holiday.').format( | ||||
| 				frappe.bold(formatdate(self.date)))) | ||||
| 
 | ||||
| def get_holiday_list(company=None): | ||||
| 	if not company: | ||||
| 		company = get_default_company() or frappe.get_all('Company')[0].name | ||||
| 
 | ||||
| 	holiday_list = frappe.get_cached_value('Company', company,  'default_holiday_list') | ||||
| 	if not holiday_list: | ||||
| 		frappe.throw(_('Please set a default Holiday List for Company {0}').format(frappe.bold(get_default_company()))) | ||||
| 	return holiday_list | ||||
|  | ||||
| @ -20,10 +20,10 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour | ||||
| 			student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \ | ||||
| 			filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") | ||||
| 
 | ||||
| 	if not student_list:  | ||||
| 		student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] ,  | ||||
| 	if not student_list: | ||||
| 		student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , | ||||
| 			filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") | ||||
| 	 | ||||
| 
 | ||||
| 	if course_schedule: | ||||
| 		student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ | ||||
| 			course_schedule= %s''', (course_schedule), as_dict=1) | ||||
| @ -32,7 +32,7 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour | ||||
| 			student_group= %s and date= %s and \ | ||||
| 			(course_schedule is Null or course_schedule='')''', | ||||
| 			(student_group, date), as_dict=1) | ||||
| 	 | ||||
| 
 | ||||
| 	for attendance in student_attendance_list: | ||||
| 		for student in student_list: | ||||
| 			if student.student == attendance.student: | ||||
|  | ||||
| @ -11,6 +11,7 @@ | ||||
|   "column_break_3", | ||||
|   "from_date", | ||||
|   "to_date", | ||||
|   "total_leave_days", | ||||
|   "section_break_5", | ||||
|   "attendance_based_on", | ||||
|   "student_group", | ||||
| @ -110,11 +111,17 @@ | ||||
|   { | ||||
|    "fieldname": "column_break_11", | ||||
|    "fieldtype": "Column Break" | ||||
|   }, | ||||
|   { | ||||
|    "fieldname": "total_leave_days", | ||||
|    "fieldtype": "Float", | ||||
|    "label": "Total Leave Days", | ||||
|    "read_only": 1 | ||||
|   } | ||||
|  ], | ||||
|  "is_submittable": 1, | ||||
|  "links": [], | ||||
|  "modified": "2020-07-08 13:22:38.329002", | ||||
|  "modified": "2020-09-21 18:10:24.440669", | ||||
|  "modified_by": "Administrator", | ||||
|  "module": "Education", | ||||
|  "name": "Student Leave Application", | ||||
|  | ||||
| @ -6,11 +6,14 @@ from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe import _ | ||||
| from datetime import timedelta | ||||
| from frappe.utils import get_link_to_form, getdate | ||||
| from frappe.utils import get_link_to_form, getdate, date_diff, flt | ||||
| from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday | ||||
| from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list | ||||
| from frappe.model.document import Document | ||||
| 
 | ||||
| class StudentLeaveApplication(Document): | ||||
| 	def validate(self): | ||||
| 		self.validate_holiday_list() | ||||
| 		self.validate_duplicate() | ||||
| 		self.validate_from_to_dates('from_date', 'to_date') | ||||
| 
 | ||||
| @ -39,10 +42,19 @@ class StudentLeaveApplication(Document): | ||||
| 			frappe.throw(_('Leave application {0} already exists against the student {1}') | ||||
| 				.format(link, frappe.bold(self.student)), title=_('Duplicate Entry')) | ||||
| 
 | ||||
| 	def validate_holiday_list(self): | ||||
| 		holiday_list = get_holiday_list() | ||||
| 		self.total_leave_days = get_number_of_leave_days(self.from_date, self.to_date, holiday_list) | ||||
| 
 | ||||
| 	def update_attendance(self): | ||||
| 		holiday_list = get_holiday_list() | ||||
| 
 | ||||
| 		for dt in daterange(getdate(self.from_date), getdate(self.to_date)): | ||||
| 			date = dt.strftime('%Y-%m-%d') | ||||
| 
 | ||||
| 			if is_holiday(holiday_list, date): | ||||
| 				continue | ||||
| 
 | ||||
| 			attendance = frappe.db.exists('Student Attendance', { | ||||
| 				'student': self.student, | ||||
| 				'date': date, | ||||
| @ -89,3 +101,19 @@ class StudentLeaveApplication(Document): | ||||
| def daterange(start_date, end_date): | ||||
| 	for n in range(int ((end_date - start_date).days)+1): | ||||
| 		yield start_date + timedelta(n) | ||||
| 
 | ||||
| def get_number_of_leave_days(from_date, to_date, holiday_list): | ||||
| 	number_of_days = date_diff(to_date, from_date) + 1 | ||||
| 
 | ||||
| 	holidays = frappe.db.sql(""" | ||||
| 		SELECT | ||||
| 			COUNT(DISTINCT holiday_date) | ||||
| 		FROM `tabHoliday` h1,`tabHoliday List` h2 | ||||
| 		WHERE | ||||
| 			h1.parent = h2.name and | ||||
| 			h1.holiday_date between %s and %s and | ||||
| 			h2.name = %s""", (from_date, to_date, holiday_list))[0][0] | ||||
| 
 | ||||
| 	number_of_days = flt(number_of_days) - flt(holidays) | ||||
| 
 | ||||
| 	return number_of_days | ||||
|  | ||||
| @ -5,13 +5,15 @@ from __future__ import unicode_literals | ||||
| 
 | ||||
| import frappe | ||||
| import unittest | ||||
| from frappe.utils import getdate, add_days | ||||
| from frappe.utils import getdate, add_days, add_months | ||||
| from erpnext import get_default_company | ||||
| from erpnext.education.doctype.student_group.test_student_group import get_random_group | ||||
| from erpnext.education.doctype.student.test_student import create_student | ||||
| 
 | ||||
| class TestStudentLeaveApplication(unittest.TestCase): | ||||
| 	def setUp(self): | ||||
| 		frappe.db.sql("""delete from `tabStudent Leave Application`""") | ||||
| 		create_holiday_list() | ||||
| 
 | ||||
| 	def test_attendance_record_creation(self): | ||||
| 		leave_application = create_leave_application() | ||||
| @ -35,20 +37,45 @@ class TestStudentLeaveApplication(unittest.TestCase): | ||||
| 		attendance_status = frappe.db.get_value('Student Attendance', {'leave_application': leave_application.name}, 'docstatus') | ||||
| 		self.assertTrue(attendance_status, 2) | ||||
| 
 | ||||
| 	def test_holiday(self): | ||||
| 		today = getdate() | ||||
| 		leave_application = create_leave_application(from_date=today, to_date= add_days(today, 1), submit=0) | ||||
| 
 | ||||
| def create_leave_application(from_date=None, to_date=None, mark_as_present=0): | ||||
| 		# holiday list validation | ||||
| 		company = get_default_company() or frappe.get_all('Company')[0].name | ||||
| 		frappe.db.set_value('Company', company, 'default_holiday_list', '') | ||||
| 		self.assertRaises(frappe.ValidationError, leave_application.save) | ||||
| 
 | ||||
| 		frappe.db.set_value('Company', company, 'default_holiday_list', 'Test Holiday List for Student') | ||||
| 		leave_application.save() | ||||
| 
 | ||||
| 		leave_application.reload() | ||||
| 		self.assertEqual(leave_application.total_leave_days, 1) | ||||
| 
 | ||||
| 		# check no attendance record created for a holiday | ||||
| 		leave_application.submit() | ||||
| 		self.assertIsNone(frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'date': add_days(today, 1)})) | ||||
| 
 | ||||
| 	def tearDown(self): | ||||
| 		company = get_default_company() or frappe.get_all('Company')[0].name | ||||
| 		frappe.db.set_value('Company', company, 'default_holiday_list', '_Test Holiday List') | ||||
| 
 | ||||
| 
 | ||||
| def create_leave_application(from_date=None, to_date=None, mark_as_present=0, submit=1): | ||||
| 	student = get_student() | ||||
| 
 | ||||
| 	leave_application = frappe.get_doc({ | ||||
| 		'doctype': 'Student Leave Application', | ||||
| 		'student': student.name, | ||||
| 		'attendance_based_on': 'Student Group', | ||||
| 		'student_group': get_random_group().name, | ||||
| 		'from_date': from_date if from_date else getdate(), | ||||
| 		'to_date': from_date if from_date else getdate(), | ||||
| 		'mark_as_present': mark_as_present | ||||
| 	}).insert() | ||||
| 	leave_application.submit() | ||||
| 	leave_application = frappe.new_doc('Student Leave Application') | ||||
| 	leave_application.student = student.name | ||||
| 	leave_application.attendance_based_on = 'Student Group' | ||||
| 	leave_application.student_group = get_random_group().name | ||||
| 	leave_application.from_date = from_date if from_date else getdate() | ||||
| 	leave_application.to_date = from_date if from_date else getdate() | ||||
| 	leave_application.mark_as_present = mark_as_present | ||||
| 
 | ||||
| 	if submit: | ||||
| 		leave_application.insert() | ||||
| 		leave_application.submit() | ||||
| 
 | ||||
| 	return leave_application | ||||
| 
 | ||||
| def create_student_attendance(date=None, status=None): | ||||
| @ -67,4 +94,22 @@ def get_student(): | ||||
| 		email='test_student@gmail.com', | ||||
| 		first_name='Test', | ||||
| 		last_name='Student' | ||||
| 	)) | ||||
| 	)) | ||||
| 
 | ||||
| def create_holiday_list(): | ||||
| 	holiday_list = 'Test Holiday List for Student' | ||||
| 	today = getdate() | ||||
| 	if not frappe.db.exists('Holiday List', holiday_list): | ||||
| 		frappe.get_doc(dict( | ||||
| 			doctype = 'Holiday List', | ||||
| 			holiday_list_name = holiday_list, | ||||
| 			from_date = add_months(today, -6), | ||||
| 			to_date = add_months(today, 6), | ||||
| 			holidays = [ | ||||
| 				dict(holiday_date=add_days(today, 1), description = 'Test') | ||||
| 			] | ||||
| 		)).insert() | ||||
| 
 | ||||
| 	company = get_default_company() or frappe.get_all('Company')[0].name | ||||
| 	frappe.db.set_value('Company', company, 'default_holiday_list', holiday_list) | ||||
| 	return holiday_list | ||||
| @ -3,8 +3,10 @@ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.utils import cstr, cint, getdate | ||||
| from frappe.utils import formatdate | ||||
| from frappe import msgprint, _ | ||||
| from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list | ||||
| from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	if not filters: filters = {} | ||||
| @ -15,6 +17,11 @@ def execute(filters=None): | ||||
| 	columns = get_columns(filters) | ||||
| 	date = filters.get("date") | ||||
| 
 | ||||
| 	holiday_list = get_holiday_list() | ||||
| 	if is_holiday(holiday_list, filters.get("date")): | ||||
| 		msgprint(_("No attendance has been marked for {0} as it is a Holiday").format(frappe.bold(formatdate(filters.get("date"))))) | ||||
| 
 | ||||
| 
 | ||||
| 	absent_students = get_absent_students(date) | ||||
| 	leave_applicants = get_leave_applications(date) | ||||
| 	if absent_students: | ||||
|  | ||||
| @ -3,8 +3,10 @@ | ||||
| 
 | ||||
| from __future__ import unicode_literals | ||||
| import frappe | ||||
| from frappe.utils import cstr, cint, getdate | ||||
| from frappe.utils import formatdate | ||||
| from frappe import msgprint, _ | ||||
| from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list | ||||
| from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	if not filters: filters = {} | ||||
| @ -12,6 +14,10 @@ def execute(filters=None): | ||||
| 	if not filters.get("date"): | ||||
| 		msgprint(_("Please select date"), raise_exception=1) | ||||
| 
 | ||||
| 	holiday_list = get_holiday_list() | ||||
| 	if is_holiday(holiday_list, filters.get("date")): | ||||
| 		msgprint(_("No attendance has been marked for {0} as it is a Holiday").format(frappe.bold(formatdate(filters.get("date"))))) | ||||
| 
 | ||||
| 	columns = get_columns(filters) | ||||
| 
 | ||||
| 	active_student_group = get_active_student_group() | ||||
|  | ||||
| @ -7,6 +7,8 @@ from frappe.utils import cstr, cint, getdate, get_first_day, get_last_day, date_ | ||||
| from frappe import msgprint, _ | ||||
| from calendar import monthrange | ||||
| from erpnext.education.api import get_student_group_students | ||||
| from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list | ||||
| from erpnext.support.doctype.issue.issue import get_holidays | ||||
| 
 | ||||
| def execute(filters=None): | ||||
| 	if not filters: filters = {} | ||||
| @ -19,26 +21,32 @@ def execute(filters=None): | ||||
| 	students_list = get_students_list(students) | ||||
| 	att_map = get_attendance_list(from_date, to_date, filters.get("student_group"), students_list) | ||||
| 	data = [] | ||||
| 
 | ||||
| 	for stud in students: | ||||
| 		row = [stud.student, stud.student_name] | ||||
| 		student_status = frappe.db.get_value("Student", stud.student, "enabled") | ||||
| 		date = from_date | ||||
| 		total_p = total_a = 0.0 | ||||
| 
 | ||||
| 		for day in range(total_days_in_month): | ||||
| 			status="None" | ||||
| 
 | ||||
| 			if att_map.get(stud.student): | ||||
| 				status = att_map.get(stud.student).get(date, "None") | ||||
| 			elif not student_status: | ||||
| 				status = "Inactive" | ||||
| 			else: | ||||
| 				status = "None" | ||||
| 			status_map = {"Present": "P", "Absent": "A", "None": "", "Inactive":"-"} | ||||
| 
 | ||||
| 			status_map = {"Present": "P", "Absent": "A", "None": "", "Inactive":"-", "Holiday":"H"} | ||||
| 			row.append(status_map[status]) | ||||
| 
 | ||||
| 			if status == "Present": | ||||
| 				total_p += 1 | ||||
| 			elif status == "Absent": | ||||
| 				total_a += 1 | ||||
| 			date = add_days(date, 1) | ||||
| 
 | ||||
| 		row += [total_p, total_a] | ||||
| 		data.append(row) | ||||
| 	return columns, data | ||||
| @ -63,14 +71,19 @@ def get_attendance_list(from_date, to_date, student_group, students_list): | ||||
| 		and date between %s and %s | ||||
| 		order by student, date''', | ||||
| 		(student_group, from_date, to_date), as_dict=1) | ||||
| 
 | ||||
| 	att_map = {} | ||||
| 	students_with_leave_application = get_students_with_leave_application(from_date, to_date, students_list) | ||||
| 	for d in attendance_list: | ||||
| 		att_map.setdefault(d.student, frappe._dict()).setdefault(d.date, "") | ||||
| 
 | ||||
| 		if students_with_leave_application.get(d.date) and d.student in students_with_leave_application.get(d.date): | ||||
| 			att_map[d.student][d.date] = "Present" | ||||
| 		else: | ||||
| 			att_map[d.student][d.date] = d.status | ||||
| 
 | ||||
| 	att_map = mark_holidays(att_map, from_date, to_date, students_list) | ||||
| 
 | ||||
| 	return att_map | ||||
| 
 | ||||
| def get_students_with_leave_application(from_date, to_date, students_list): | ||||
| @ -108,3 +121,14 @@ def get_attendance_years(): | ||||
| 	if not year_list: | ||||
| 		year_list = [getdate().year] | ||||
| 	return "\n".join(str(year) for year in year_list) | ||||
| 
 | ||||
| def mark_holidays(att_map, from_date, to_date, students_list): | ||||
| 	holiday_list = get_holiday_list() | ||||
| 	holidays = get_holidays(holiday_list) | ||||
| 
 | ||||
| 	for dt in daterange(getdate(from_date), getdate(to_date)): | ||||
| 		if dt in holidays: | ||||
| 			for student in students_list: | ||||
| 				att_map.setdefault(student, frappe._dict()).setdefault(dt, "Holiday") | ||||
| 
 | ||||
| 	return att_map | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user