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

View File

@ -25,7 +25,7 @@ class Student(Document):
for sibling in self.siblings: for sibling in self.siblings:
if sibling.date_of_birth and getdate(sibling.date_of_birth) > getdate(): 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)) 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()): if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
frappe.throw(_("Date of Birth cannot be greater than 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 from `tabStudent Attendance` where
student=%s student=%s
and `date` > date_sub(curdate(), interval 1 year) and `date` > date_sub(curdate(), interval 1 year)
and status = 'Present' and docstatus = 1 and status = 'Present'
group by date''', name)) group by date''', name))

View File

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

View File

@ -6,52 +6,63 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ 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 from erpnext.education.api import get_student_group_students
class StudentAttendance(Document): class StudentAttendance(Document):
def validate(self): def validate(self):
self.validate_date()
self.validate_mandatory() self.validate_mandatory()
self.validate_course_schedule() self.set_date()
self.set_student_group()
self.validate_student() self.validate_student()
self.validate_duplication() self.validate_duplication()
def validate_date(self): def set_date(self):
if self.course_schedule: 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): def validate_mandatory(self):
if not (self.student_group or self.course_schedule): if not (self.student_group or self.course_schedule):
frappe.throw(_("""Student Group or Course Schedule is mandatory""")) frappe.throw(_('{0} or {1} is mandatory').format(frappe.bold('Student Group'),
frappe.bold('Course Schedule')), title=_('Mandatory Fields'))
def validate_course_schedule(self):
def set_student_group(self):
if self.course_schedule: 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): def validate_student(self):
if self.course_schedule: 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: else:
student_group = self.student_group student_group = self.student_group
student_group_students = [d.student for d in get_student_group_students(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: 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): def validate_duplication(self):
"""Check if the Attendance Record is Unique""" """Check if the Attendance Record is Unique"""
attendance_records=None attendance_record = None
if self.course_schedule: if self.course_schedule:
attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \ attendance_record = frappe.db.exists('Student Attendance', {
student= %s and ifnull(course_schedule, '')= %s and name != %s""", 'student': self.student,
(self.student, cstr(self.course_schedule), self.name)) 'course_schedule': self.course_schedule,
'docstatus': ('!=', 2),
'name': ('!=', self.name)
})
else: else:
attendance_records= frappe.db.sql("""select name from `tabStudent Attendance` where \ attendance_record = frappe.db.exists('Student Attendance', {
student= %s and student_group= %s and date= %s and name != %s and \ 'student': self.student,
(course_schedule is Null or course_schedule='')""", 'student_group': self.student_group,
(self.student, self.student_group, self.date, self.name)) 'date': self.date,
'docstatus': ('!=', 2),
if attendance_records: 'name': ('!=', self.name),
frappe.throw(_("Attendance Record {0} exists against Student {1}") 'course_schedule': ''
.format(attendance_records[0][0], self.student)) })
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, "actions": [],
"allow_guest_to_view": 0, "autoname": "EDU-SLA-.YYYY.-.#####",
"allow_import": 0, "creation": "2016-11-28 15:38:54.793854",
"allow_rename": 0, "doctype": "DocType",
"autoname": "EDU-SLA-.YYYY.-.#####", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2016-11-28 15:38:54.793854", "field_order": [
"custom": 0, "student",
"docstatus": 0, "student_name",
"doctype": "DocType", "column_break_3",
"document_type": "", "from_date",
"editable_grid": 1, "to_date",
"engine": "InnoDB", "section_break_5",
"attendance_based_on",
"student_group",
"course_schedule",
"mark_as_present",
"column_break_11",
"reason",
"amended_from"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "student",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_global_search": 1,
"bold": 0, "label": "Student",
"collapsible": 0, "options": "Student",
"columns": 0, "reqd": 1
"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
},
{ {
"allow_bulk_edit": 0, "fetch_from": "student.title",
"allow_in_quick_entry": 0, "fieldname": "student_name",
"allow_on_submit": 0, "fieldtype": "Read Only",
"bold": 0, "in_global_search": 1,
"collapsible": 0, "label": "Student Name",
"columns": 0, "read_only": 1
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_3",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "from_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "From Date",
"columns": 0, "reqd": 1
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "to_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "To Date",
"collapsible": 0, "reqd": 1
"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
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 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",
"allow_on_submit": 0, "fieldname": "mark_as_present",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "label": "Mark as Present"
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "reason",
"allow_in_quick_entry": 0, "fieldtype": "Text",
"allow_on_submit": 0, "label": "Reason"
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "amended_from",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Amended From",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Student Leave Application",
"columns": 0, "print_hide": 1,
"fieldname": "amended_from", "read_only": 1
"fieldtype": "Link", },
"hidden": 0, {
"ignore_user_permissions": 0, "allow_in_quick_entry": 1,
"ignore_xss_filter": 0, "default": "Student Group",
"in_filter": 0, "fieldname": "attendance_based_on",
"in_global_search": 0, "fieldtype": "Select",
"in_list_view": 0, "label": "Attendance Based On",
"in_standard_filter": 0, "options": "Student Group\nCourse Schedule"
"label": "Amended From", },
"length": 0, {
"no_copy": 1, "allow_in_quick_entry": 1,
"options": "Student Leave Application", "depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
"permlevel": 0, "fieldname": "student_group",
"print_hide": 1, "fieldtype": "Link",
"print_hide_if_no_value": 0, "label": "Student Group",
"read_only": 1, "mandatory_depends_on": "eval:doc.attendance_based_on === \"Student Group\";",
"remember_last_selected_value": 0, "options": "Student Group"
"report_hide": 0, },
"reqd": 0, {
"search_index": 0, "allow_in_quick_entry": 1,
"set_only_once": 0, "depends_on": "eval:doc.attendance_based_on === \"Course Schedule\";",
"translatable": 0, "fieldname": "course_schedule",
"unique": 0 "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, "is_submittable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-07-08 13:22:38.329002",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Education",
"in_create": 0, "name": "Student Leave Application",
"is_submittable": 1, "owner": "Administrator",
"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",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0, "print": 1,
"if_owner": 0, "read": 1,
"import": 0, "report": 1,
"permlevel": 0, "role": "Instructor",
"print": 1, "submit": 1,
"read": 1,
"report": 1,
"role": "Instructor",
"set_user_permissions": 0,
"share": 0,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "Academics User",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "restrict_to_domain": "Education",
"read_only_onload": 0, "show_name_in_global_search": 1,
"restrict_to_domain": "Education", "sort_field": "modified",
"show_name_in_global_search": 1, "sort_order": "DESC",
"sort_field": "modified", "title_field": "student_name"
"sort_order": "DESC",
"title_field": "student_name",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -5,17 +5,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ 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.model.document import Document
from frappe import throw, _
class StudentLeaveApplication(Document): class StudentLeaveApplication(Document):
def validate(self): def validate(self):
self.validate_dates()
self.validate_duplicate() 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): 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 where
((%(from_date)s > from_date and %(from_date)s < to_date) or ((%(from_date)s > from_date and %(from_date)s < to_date) or
(%(to_date)s > from_date and %(to_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) }, as_dict=1)
if data: if data:
link = get_link_to_form("Student Leave Application", data[0].name) link = get_link_to_form('Student Leave Application', data[0].name)
frappe.throw(_("Leave application {0} already exists against the student {1}") frappe.throw(_('Leave application {0} already exists against the student {1}')
.format(link, self.student)) .format(link, frappe.bold(self.student)), title=_('Duplicate Entry'))
def validate_dates(self): def update_attendance(self):
if self.to_date < self.from_date : for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
throw(_("To Date cannot be less than From 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 frappe
import unittest import unittest
from frappe.utils import getdate, add_days
# test_records = frappe.get_test_records('Student Leave Application') 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): 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"]) from_date, to_date = frappe.db.get_value("Academic Term", academic_term, ["term_start_date", "term_end_date"])
if from_date and to_date: if from_date and to_date:
attendance = dict(frappe.db.sql('''select status, count(student) as no_of_days 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''', and date between %s and %s group by status''',
(student, from_date, to_date))) (student, from_date, to_date)))
if "Absent" not in attendance.keys(): if "Absent" not in attendance.keys():

View File

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

View File

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

View File

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