feat(Education): Student Attendance and Leave Enhancements (#22623)

* feat: make Student Attendance doctype submittable

* feat: add attendance related fields in Student Leave Application

* feat: update Attendance records on Leave Application submission

* refactor: better error messages and ORM queries

* fix: show present only for leave applications with mark_as_present enabled in attendance reports

* test: Student Leave Application

* fix: filter for attendance records

* fix: codacy issues
This commit is contained in:
Rucha Mahabal 2020-07-23 16:40:07 +05:30 committed by GitHub
parent 04a76285e2
commit 833682b03d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 468 additions and 698 deletions

View File

@ -104,6 +104,7 @@ def make_attendance_records(student, student_name, status, course_schedule=None,
student_attendance.date = date
student_attendance.status = status
student_attendance.save()
student_attendance.submit()
@frappe.whitelist()
@ -363,9 +364,9 @@ def get_current_enrollment(student, academic_year=None):
select
name as program_enrollment, student_name, program, student_batch_name as student_batch,
student_category, academic_term, academic_year
from
from
`tabProgram Enrollment`
where
where
student = %s and academic_year = %s
order by creation''', (student, current_academic_year), as_dict=1)

View File

@ -25,7 +25,7 @@ class Student(Document):
for sibling in self.siblings:
if sibling.date_of_birth and getdate(sibling.date_of_birth) > getdate():
frappe.throw(_("Row {0}:Sibling Date of Birth cannot be greater than today.").format(sibling.idx))
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
frappe.throw(_("Date of Birth cannot be greater than today."))
@ -157,5 +157,5 @@ def get_timeline_data(doctype, name):
from `tabStudent Attendance` where
student=%s
and `date` > date_sub(curdate(), interval 1 year)
and status = 'Present'
and docstatus = 1 and status = 'Present'
group by date''', name))

View File

@ -1,287 +1,125 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2015-11-05 15:20:23.045996",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2015-11-05 15:20:23.045996",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"naming_series",
"student",
"student_name",
"course_schedule",
"student_group",
"column_break_3",
"date",
"status",
"leave_application",
"amended_from"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Student",
"length": 0,
"no_copy": 0,
"options": "Student",
"permlevel": 0,
"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": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "student",
"fieldtype": "Link",
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Student",
"options": "Student",
"reqd": 1,
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "course_schedule",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Course Schedule",
"length": 0,
"no_copy": 0,
"options": "Course Schedule",
"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
},
"fieldname": "course_schedule",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course Schedule",
"options": "Course Schedule"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "date",
"fieldtype": "Date",
"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": "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": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "date",
"fieldtype": "Date",
"label": "Date",
"reqd": 1,
"search_index": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"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
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student.title",
"fieldname": "student_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Name",
"length": 0,
"no_copy": 0,
"options": "",
"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
},
"fieldname": "student_name",
"fieldtype": "Read Only",
"in_global_search": 1,
"label": "Student Name"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Student Group",
"length": 0,
"no_copy": 0,
"options": "Student Group",
"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
},
"fieldname": "student_group",
"fieldtype": "Link",
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Student Group",
"options": "Student Group"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Present",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Present\nAbsent",
"permlevel": 0,
"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
"default": "Present",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "Present\nAbsent",
"reqd": 1
},
{
"fieldname": "leave_application",
"fieldtype": "Link",
"label": "Leave Application",
"options": "Student Leave Application",
"read_only": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "EDU-ATT-.YYYY.-"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Student Attendance",
"print_hide": 1,
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-27 10:48:22.301531",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Attendance",
"name_case": "",
"owner": "Administrator",
],
"is_submittable": 1,
"links": [],
"modified": "2020-07-08 13:55:42.580181",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Attendance",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "student_name",
"track_changes": 0,
"track_seen": 0
],
"restrict_to_domain": "Education",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "student_name"
}

View File

@ -6,52 +6,63 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe import _
from frappe.utils import cstr
from frappe.utils import get_link_to_form
from erpnext.education.api import get_student_group_students
class StudentAttendance(Document):
def validate(self):
self.validate_date()
self.validate_mandatory()
self.validate_course_schedule()
self.set_date()
self.set_student_group()
self.validate_student()
self.validate_duplication()
def validate_date(self):
def set_date(self):
if self.course_schedule:
self.date = frappe.db.get_value("Course Schedule", self.course_schedule, "schedule_date")
self.date = frappe.db.get_value('Course Schedule', self.course_schedule, 'schedule_date')
def validate_mandatory(self):
if not (self.student_group or self.course_schedule):
frappe.throw(_("""Student Group or Course Schedule is mandatory"""))
def validate_course_schedule(self):
frappe.throw(_('{0} or {1} is mandatory').format(frappe.bold('Student Group'),
frappe.bold('Course Schedule')), title=_('Mandatory Fields'))
def set_student_group(self):
if self.course_schedule:
self.student_group = frappe.db.get_value("Course Schedule", self.course_schedule, "student_group")
self.student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
def validate_student(self):
if self.course_schedule:
student_group = frappe.db.get_value("Course Schedule", self.course_schedule, "student_group")
student_group = frappe.db.get_value('Course Schedule', self.course_schedule, 'student_group')
else:
student_group = self.student_group
student_group_students = [d.student for d in get_student_group_students(student_group)]
if student_group and self.student not in student_group_students:
frappe.throw(_('''Student {0}: {1} does not belong to Student Group {2}'''.format(self.student, self.student_name, student_group)))
student_group_doc = get_link_to_form('Student Group', student_group)
frappe.throw(_('Student {0}: {1} does not belong to Student Group {2}').format(
frappe.bold(self.student), self.student_name, frappe.bold(student_group_doc)))
def validate_duplication(self):
"""Check if the Attendance Record is Unique"""
attendance_records=None
attendance_record = None
if self.course_schedule:
attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \
student= %s and ifnull(course_schedule, '')= %s and name != %s""",
(self.student, cstr(self.course_schedule), self.name))
attendance_record = frappe.db.exists('Student Attendance', {
'student': self.student,
'course_schedule': self.course_schedule,
'docstatus': ('!=', 2),
'name': ('!=', self.name)
})
else:
attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \
student= %s and student_group= %s and date= %s and name != %s and \
(course_schedule is Null or course_schedule='')""",
(self.student, self.student_group, self.date, self.name))
if attendance_records:
frappe.throw(_("Attendance Record {0} exists against Student {1}")
.format(attendance_records[0][0], self.student))
attendance_record = frappe.db.exists('Student Attendance', {
'student': self.student,
'student_group': self.student_group,
'date': self.date,
'docstatus': ('!=', 2),
'name': ('!=', self.name),
'course_schedule': ''
})
if attendance_record:
record = get_link_to_form('Attendance Record', attendance_record)
frappe.throw(_('Student Attendance record {0} already exists against the Student {1}')
.format(record, frappe.bold(self.student)), title=_('Duplicate Entry'))

View File

@ -1,375 +1,158 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "EDU-SLA-.YYYY.-.#####",
"beta": 0,
"creation": "2016-11-28 15:38:54.793854",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"autoname": "EDU-SLA-.YYYY.-.#####",
"creation": "2016-11-28 15:38:54.793854",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"student",
"student_name",
"column_break_3",
"from_date",
"to_date",
"section_break_5",
"attendance_based_on",
"student_group",
"course_schedule",
"mark_as_present",
"column_break_11",
"reason",
"amended_from"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student",
"length": 0,
"no_copy": 0,
"options": "Student",
"permlevel": 0,
"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
},
"fieldname": "student",
"fieldtype": "Link",
"in_global_search": 1,
"label": "Student",
"options": "Student",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "student.title",
"fieldname": "student_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Student Name",
"length": 0,
"no_copy": 0,
"options": "",
"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
},
"fetch_from": "student.title",
"fieldname": "student_name",
"fieldtype": "Read Only",
"in_global_search": 1,
"label": "Student Name",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"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,
"label": "",
"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
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_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_standard_filter": 1,
"label": "From 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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "from_date",
"fieldtype": "Date",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "From Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_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_standard_filter": 0,
"label": "To 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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "to_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "To Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Will show the student as Present in Student Monthly Attendance Report",
"fieldname": "mark_as_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": "Mark as 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
},
"default": "0",
"description": "Check this to mark the student as present in case the student is not attending the institute to participate or represent the institute in any event.\n\n",
"fieldname": "mark_as_present",
"fieldtype": "Check",
"label": "Mark as Present"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section 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
},
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reason",
"fieldtype": "Text",
"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": "Reason",
"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
},
"fieldname": "reason",
"fieldtype": "Text",
"label": "Reason"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"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",
"length": 0,
"no_copy": 1,
"options": "Student Leave Application",
"permlevel": 0,
"print_hide": 1,
"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
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Student Leave Application",
"print_hide": 1,
"read_only": 1
},
{
"allow_in_quick_entry": 1,
"default": "Student Group",
"fieldname": "attendance_based_on",
"fieldtype": "Select",
"label": "Attendance Based On",
"options": "Student Group\nCourse Schedule"
},
{
"allow_in_quick_entry": 1,
"depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
"fieldname": "student_group",
"fieldtype": "Link",
"label": "Student Group",
"mandatory_depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
"options": "Student Group"
},
{
"allow_in_quick_entry": 1,
"depends_on": "eval:doc.attendance_based_on === \"Course Schedule\";",
"fieldname": "course_schedule",
"fieldtype": "Link",
"label": "Course Schedule",
"mandatory_depends_on": "eval:doc.attendance_based_on === \"Course Schedule\";",
"options": "Course Schedule"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:50.807352",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Leave Application",
"name_case": "",
"owner": "Administrator",
],
"is_submittable": 1,
"links": [],
"modified": "2020-07-08 13:22:38.329002",
"modified_by": "Administrator",
"module": "Education",
"name": "Student Leave Application",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
"set_user_permissions": 0,
"share": 0,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Instructor",
"submit": 1,
"write": 1
},
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "student_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
],
"quick_entry": 1,
"restrict_to_domain": "Education",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "student_name"
}

View File

@ -5,17 +5,23 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import get_link_to_form
from datetime import timedelta
from frappe.utils import get_link_to_form, getdate
from frappe.model.document import Document
from frappe import throw, _
class StudentLeaveApplication(Document):
def validate(self):
self.validate_dates()
self.validate_duplicate()
self.validate_from_to_dates('from_date', 'to_date')
def on_submit(self):
self.update_attendance()
def on_cancel(self):
self.cancel_attendance()
def validate_duplicate(self):
data = frappe.db.sql(""" select name from `tabStudent Leave Application`
data = frappe.db.sql("""select name from `tabStudent Leave Application`
where
((%(from_date)s > from_date and %(from_date)s < to_date) or
(%(to_date)s > from_date and %(to_date)s < to_date) or
@ -29,10 +35,57 @@ class StudentLeaveApplication(Document):
}, as_dict=1)
if data:
link = get_link_to_form("Student Leave Application", data[0].name)
frappe.throw(_("Leave application {0} already exists against the student {1}")
.format(link, self.student))
link = get_link_to_form('Student Leave Application', data[0].name)
frappe.throw(_('Leave application {0} already exists against the student {1}')
.format(link, frappe.bold(self.student)), title=_('Duplicate Entry'))
def validate_dates(self):
if self.to_date < self.from_date :
throw(_("To Date cannot be less than From Date"))
def update_attendance(self):
for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
date = dt.strftime('%Y-%m-%d')
attendance = frappe.db.exists('Student Attendance', {
'student': self.student,
'date': date,
'docstatus': ('!=', 2)
})
status = 'Present' if self.mark_as_present else 'Absent'
if attendance:
# update existing attendance record
values = dict()
values['status'] = status
values['leave_application'] = self.name
frappe.db.set_value('Student Attendance', attendance, values)
else:
# make a new attendance record
doc = frappe.new_doc('Student Attendance')
doc.student = self.student
doc.student_name = self.student_name
doc.date = date
doc.leave_application = self.name
doc.status = status
if self.attendance_based_on == 'Student Group':
doc.student_group = self.student_group
else:
doc.course_schedule = self.course_schedule
doc.insert(ignore_permissions=True, ignore_mandatory=True)
doc.submit()
def cancel_attendance(self):
if self.docstatus == 2:
attendance = frappe.db.sql("""
SELECT name
FROM `tabStudent Attendance`
WHERE
student = %s and
(date between %s and %s) and
docstatus < 2
""", (self.student, self.from_date, self.to_date), as_dict=1)
for name in attendance:
frappe.db.set_value('Student Attendance', name, 'docstatus', 2)
def daterange(start_date, end_date):
for n in range(int ((end_date - start_date).days)+1):
yield start_date + timedelta(n)

View File

@ -0,0 +1,11 @@
from __future__ import unicode_literals
def get_data():
return {
'fieldname': 'leave_application',
'transactions': [
{
'items': ['Student Attendance']
}
]
}

View File

@ -5,8 +5,66 @@ from __future__ import unicode_literals
import frappe
import unittest
# test_records = frappe.get_test_records('Student Leave Application')
from frappe.utils import getdate, add_days
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):
pass
def setUp(self):
frappe.db.sql("""delete from `tabStudent Leave Application`""")
def test_attendance_record_creation(self):
leave_application = create_leave_application()
attendance_record = frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'status': 'Absent'})
self.assertTrue(attendance_record)
# mark as present
date = add_days(getdate(), -1)
leave_application = create_leave_application(date, date, 1)
attendance_record = frappe.db.exists('Student Attendance', {'leave_application': leave_application.name, 'status': 'Present'})
self.assertTrue(attendance_record)
def test_attendance_record_updated(self):
attendance = create_student_attendance()
create_leave_application()
self.assertEqual(frappe.db.get_value('Student Attendance', attendance.name, 'status'), 'Absent')
def test_attendance_record_cancellation(self):
leave_application = create_leave_application()
leave_application.cancel()
attendance_status = frappe.db.get_value('Student Attendance', {'leave_application': leave_application.name}, 'docstatus')
self.assertTrue(attendance_status, 2)
def create_leave_application(from_date=None, to_date=None, mark_as_present=0):
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()
return leave_application
def create_student_attendance(date=None, status=None):
student = get_student()
attendance = frappe.get_doc({
'doctype': 'Student Attendance',
'student': student.name,
'status': status if status else 'Present',
'date': date if date else getdate(),
'student_group': get_random_group().name
}).insert()
return attendance
def get_student():
return create_student(dict(
email='test_student@gmail.com',
first_name='Test',
last_name='Student'
))

View File

@ -80,7 +80,7 @@ def get_attendance_count(student, academic_year, academic_term=None):
from_date, to_date = frappe.db.get_value("Academic Term", academic_term, ["term_start_date", "term_end_date"])
if from_date and to_date:
attendance = dict(frappe.db.sql('''select status, count(student) as no_of_days
from `tabStudent Attendance` where student = %s
from `tabStudent Attendance` where student = %s and docstatus = 1
and date between %s and %s group by status''',
(student, from_date, to_date)))
if "Absent" not in attendance.keys():

View File

@ -11,7 +11,7 @@ def execute(filters=None):
if not filters.get("date"):
msgprint(_("Please select date"), raise_exception=1)
columns = get_columns(filters)
date = filters.get("date")
@ -26,27 +26,27 @@ def execute(filters=None):
if not student.student in leave_applicants:
row = [student.student, student.student_name, student.student_group]
stud_details = frappe.db.get_value("Student", student.student, ['student_email_id', 'student_mobile_number'], as_dict=True)
if stud_details.student_email_id:
row+=[stud_details.student_email_id]
else:
row+= [""]
if stud_details.student_mobile_number:
row+=[stud_details.student_mobile_number]
else:
row+= [""]
if transportation_details.get(student.student):
row += transportation_details.get(student.student)
data.append(row)
return columns, data
def get_columns(filters):
columns = [
_("Student") + ":Link/Student:90",
_("Student Name") + "::150",
columns = [
_("Student") + ":Link/Student:90",
_("Student Name") + "::150",
_("Student Group") + "::180",
_("Student Email Address") + "::180",
_("Student Mobile No.") + "::150",
@ -56,15 +56,29 @@ def get_columns(filters):
return columns
def get_absent_students(date):
absent_students = frappe.db.sql("""select student, student_name, student_group from `tabStudent Attendance`
where status="Absent" and date = %s order by student_group, student_name""", date, as_dict=1)
absent_students = frappe.db.sql("""
SELECT student, student_name, student_group
FROM `tabStudent Attendance`
WHERE
status='Absent' and docstatus=1 and date = %s
ORDER BY
student_group, student_name""",
date, as_dict=1)
return absent_students
def get_leave_applications(date):
leave_applicants = []
for student in frappe.db.sql("""select student from `tabStudent Leave Application`
where docstatus = 1 and from_date <= %s and to_date >= %s""", (date, date)):
leave_applications = frappe.db.sql("""
SELECT student
FROM
`tabStudent Leave Application`
WHERE
docstatus = 1 and mark_as_present = 1 and
from_date <= %s and to_date >= %s
""", (date, date))
for student in leave_applications:
leave_applicants.append(student[0])
return leave_applicants
def get_transportation_details(date, student_list):

View File

@ -11,7 +11,7 @@ def execute(filters=None):
if not filters.get("date"):
msgprint(_("Please select date"), raise_exception=1)
columns = get_columns(filters)
active_student_group = get_active_student_group()
@ -37,28 +37,28 @@ def execute(filters=None):
return columns, data
def get_columns(filters):
columns = [
_("Student Group") + ":Link/Student Group:250",
_("Student Group Strength") + "::170",
_("Present") + "::90",
columns = [
_("Student Group") + ":Link/Student Group:250",
_("Student Group Strength") + "::170",
_("Present") + "::90",
_("Absent") + "::90",
_("Not Marked") + "::90"
]
return columns
def get_active_student_group():
active_student_groups = frappe.db.sql("""select name from `tabStudent Group` where group_based_on = "Batch"
active_student_groups = frappe.db.sql("""select name from `tabStudent Group` where group_based_on = "Batch"
and academic_year=%s order by name""", (frappe.defaults.get_defaults().academic_year), as_dict=1)
return active_student_groups
def get_student_group_strength(student_group):
student_group_strength = frappe.db.sql("""select count(*) from `tabStudent Group Student`
student_group_strength = frappe.db.sql("""select count(*) from `tabStudent Group Student`
where parent = %s and active=1""", student_group)[0][0]
return student_group_strength
def get_student_attendance(student_group, date):
student_attendance = frappe.db.sql("""select count(*) as count, status from `tabStudent Attendance` where \
student_group= %s and date= %s and\
student_attendance = frappe.db.sql("""select count(*) as count, status from `tabStudent Attendance` where
student_group= %s and date= %s and docstatus = 1 and
(course_schedule is Null or course_schedule='') group by status""",
(student_group, date), as_dict=1)
return student_attendance

View File

@ -57,8 +57,9 @@ def get_students_list(students):
return student_list
def get_attendance_list(from_date, to_date, student_group, students_list):
attendance_list = frappe.db.sql('''select student, date, status
from `tabStudent Attendance` where student_group = %s
attendance_list = frappe.db.sql('''select student, date, status
from `tabStudent Attendance` where student_group = %s
and docstatus = 1
and date between %s and %s
order by student, date''',
(student_group, from_date, to_date), as_dict=1)
@ -75,10 +76,10 @@ def get_attendance_list(from_date, to_date, student_group, students_list):
def get_students_with_leave_application(from_date, to_date, students_list):
if not students_list: return
leave_applications = frappe.db.sql("""
select student, from_date, to_date
from `tabStudent Leave Application`
where
mark_as_present and docstatus = 1
select student, from_date, to_date
from `tabStudent Leave Application`
where
mark_as_present = 1 and docstatus = 1
and student in %(students)s
and (
from_date between %(from_date)s and %(to_date)s