From d5ca86c0e69f3d8d031b1dc6fd21947b0d824a20 Mon Sep 17 00:00:00 2001 From: Anoop Date: Fri, 21 Oct 2022 20:38:59 +0530 Subject: [PATCH] Rebranded --- .gitignore | 6 + .idea/.gitignore | 8 + .idea/deployment.xml | 49 ++ .idea/education.iml | 8 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .pre-commit-config.yaml | 34 + MANIFEST.in | 18 + README.md | 24 + education/__init__.py | 1 + education/config/__init__.py | 0 education/config/desktop.py | 13 + education/config/docs.py | 11 + education/education/__init__.py | 19 + education/education/api.py | 483 +++++++++++ education/education/doctype/__init__.py | 0 .../doctype/academic_term/__init__.py | 0 .../doctype/academic_term/academic_term.js | 8 + .../doctype/academic_term/academic_term.json | 216 +++++ .../doctype/academic_term/academic_term.py | 74 ++ .../academic_term/academic_term_dashboard.py | 16 + .../academic_term/test_academic_term.py | 10 + .../doctype/academic_term/test_records.json | 27 + .../doctype/academic_year/__init__.py | 0 .../doctype/academic_year/academic_year.js | 2 + .../doctype/academic_year/academic_year.json | 154 ++++ .../doctype/academic_year/academic_year.py | 22 + .../academic_year/academic_year_dashboard.py | 19 + .../academic_year/test_academic_year.py | 10 + .../doctype/academic_year/test_records.json | 18 + .../education/doctype/article/__init__.py | 0 .../education/doctype/article/article.js | 56 ++ .../education/doctype/article/article.json | 81 ++ .../education/doctype/article/article.py | 22 + .../education/doctype/article/test_article.py | 8 + .../doctype/assessment_criteria/__init__.py | 0 .../assessment_criteria.js | 8 + .../assessment_criteria.json | 125 +++ .../assessment_criteria.py | 22 + .../test_assessment_criteria.py | 10 + .../assessment_criteria/test_records.json | 8 + .../assessment_criteria_group/__init__.py | 0 .../assessment_criteria_group.js | 8 + .../assessment_criteria_group.json | 94 +++ .../assessment_criteria_group.py | 9 + .../test_assessment_criteria_group.py | 10 + .../doctype/assessment_group/__init__.py | 0 .../assessment_group/assessment_group.js | 8 + .../assessment_group/assessment_group.json | 90 +++ .../assessment_group/assessment_group.py | 9 + .../assessment_group_dashboard.py | 13 + .../assessment_group/assessment_group_tree.js | 3 + .../assessment_group/test_assessment_group.py | 10 + .../doctype/assessment_plan/__init__.py | 0 .../assessment_plan/assessment_plan.js | 78 ++ .../assessment_plan/assessment_plan.json | 227 ++++++ .../assessment_plan/assessment_plan.py | 60 ++ .../assessment_plan_dashboard.py | 12 + .../assessment_plan/test_assessment_plan.py | 10 + .../assessment_plan_criteria/__init__.py | 0 .../assessment_plan_criteria.json | 134 +++ .../assessment_plan_criteria.py | 9 + .../doctype/assessment_result/__init__.py | 0 .../assessment_result/assessment_result.js | 125 +++ .../assessment_result/assessment_result.json | 203 +++++ .../assessment_result/assessment_result.py | 59 ++ .../assessment_result_dashboard.py | 15 + .../test_assessment_result.py | 17 + .../assessment_result_detail/__init__.py | 0 .../assessment_result_detail.json | 66 ++ .../assessment_result_detail.py | 9 + .../assessment_result_tool/__init__.py | 0 .../assessment_result_tool.js | 162 ++++ .../assessment_result_tool.json | 235 ++++++ .../assessment_result_tool.py | 9 + .../test_assessment_result_tool.py | 8 + .../doctype/content_activity/__init__.py | 0 .../content_activity/content_activity.json | 141 ++++ .../content_activity/content_activity.py | 9 + .../doctype/content_question/__init__.py | 0 .../content_question/content_question.js | 8 + .../content_question/content_question.json | 76 ++ .../content_question/content_question.py | 9 + .../content_question/test_content_question.py | 8 + .../education/doctype/course/__init__.py | 0 education/education/doctype/course/course.js | 79 ++ .../education/doctype/course/course.json | 137 ++++ education/education/doctype/course/course.py | 61 ++ .../doctype/course/course_dashboard.py | 18 + .../education/doctype/course/test_course.py | 52 ++ .../doctype/course/test_records.json | 14 + .../doctype/course_activity/__init__.py | 0 .../course_activity/course_activity.js | 8 + .../course_activity/course_activity.json | 301 +++++++ .../course_activity/course_activity.py | 18 + .../course_activity/test_course_activity.py | 30 + .../course_assessment_criteria/__init__.py | 0 .../course_assessment_criteria.json | 134 +++ .../course_assessment_criteria.py | 9 + .../doctype/course_content/__init__.py | 0 .../doctype/course_content/course_content.js | 8 + .../course_content/course_content.json | 142 ++++ .../doctype/course_content/course_content.py | 9 + .../course_content/test_course_content.py | 8 + .../doctype/course_enrollment/__init__.py | 0 .../course_enrollment/course_enrollment.js | 8 + .../course_enrollment/course_enrollment.json | 233 ++++++ .../course_enrollment/course_enrollment.py | 114 +++ .../course_enrollment_dashboard.py | 13 + .../test_course_enrollment.py | 67 ++ .../doctype/course_schedule/__init__.py | 0 .../course_schedule/course_schedule.js | 16 + .../course_schedule/course_schedule.json | 143 ++++ .../course_schedule/course_schedule.py | 68 ++ .../course_schedule_calendar.js | 38 + .../course_schedule_dashboard.py | 11 + .../course_schedule/test_course_schedule.py | 102 +++ .../course_scheduling_tool/__init__.py | 0 .../course_scheduling_tool.js | 102 +++ .../course_scheduling_tool.json | 171 ++++ .../course_scheduling_tool.py | 119 +++ .../test_course_scheduling_tool.py | 8 + .../doctype/course_topic/__init__.py | 0 .../doctype/course_topic/course_topic.js | 8 + .../doctype/course_topic/course_topic.json | 109 +++ .../doctype/course_topic/course_topic.py | 9 + .../doctype/course_topic/test_course_topic.py | 8 + .../doctype/education_settings/__init__.py | 0 .../education_settings/education_settings.js | 8 + .../education_settings.json | 149 ++++ .../education_settings/education_settings.py | 55 ++ .../test_education_settings.py | 8 + .../doctype/fee_category/__init__.py | 0 .../doctype/fee_category/fee_category.js | 8 + .../doctype/fee_category/fee_category.json | 171 ++++ .../doctype/fee_category/fee_category.py | 9 + .../doctype/fee_category/test_fee_category.py | 10 + .../doctype/fee_category/test_records.json | 11 + .../doctype/fee_component/__init__.py | 0 .../doctype/fee_component/fee_component.json | 169 ++++ .../doctype/fee_component/fee_component.py | 9 + .../doctype/fee_schedule/__init__.py | 0 .../doctype/fee_schedule/fee_schedule.js | 133 +++ .../doctype/fee_schedule/fee_schedule.json | 327 ++++++++ .../doctype/fee_schedule/fee_schedule.py | 188 +++++ .../fee_schedule/fee_schedule_dashboard.py | 6 + .../doctype/fee_schedule/fee_schedule_list.js | 14 + .../doctype/fee_schedule/test_fee_schedule.py | 8 + .../doctype/fee_schedule_program/__init__.py | 0 .../fee_schedule_program.json | 142 ++++ .../fee_schedule_program.py | 9 + .../fee_schedule_student_group/__init__.py | 0 .../fee_schedule_student_group.json | 109 +++ .../fee_schedule_student_group.py | 9 + .../doctype/fee_structure/__init__.py | 0 .../doctype/fee_structure/fee_structure.js | 72 ++ .../doctype/fee_structure/fee_structure.json | 215 +++++ .../doctype/fee_structure/fee_structure.py | 36 + .../fee_structure/fee_structure_dashboard.py | 11 + .../fee_structure/test_fee_structure.py | 10 + .../doctype/fee_structure/test_records.json | 42 + education/education/doctype/fees/__init__.py | 0 education/education/doctype/fees/fees.js | 199 +++++ education/education/doctype/fees/fees.json | 395 +++++++++ education/education/doctype/fees/fees.py | 168 ++++ education/education/doctype/fees/fees_list.js | 12 + education/education/doctype/fees/test_fees.py | 60 ++ .../doctype/grading_scale/__init__.py | 0 .../doctype/grading_scale/grading_scale.js | 8 + .../doctype/grading_scale/grading_scale.json | 226 ++++++ .../doctype/grading_scale/grading_scale.py | 20 + .../grading_scale/grading_scale_dashboard.py | 12 + .../grading_scale/test_grading_scale.py | 10 + .../doctype/grading_scale/test_records.json | 19 + .../grading_scale_interval/__init__.py | 0 .../grading_scale_interval.json | 133 +++ .../grading_scale_interval.py | 9 + .../education/doctype/guardian/__init__.py | 0 .../education/doctype/guardian/guardian.js | 20 + .../education/doctype/guardian/guardian.json | 580 +++++++++++++ .../education/doctype/guardian/guardian.py | 61 ++ .../doctype/guardian/test_guardian.py | 10 + .../doctype/guardian_interest/__init__.py | 0 .../guardian_interest/guardian_interest.json | 72 ++ .../guardian_interest/guardian_interest.py | 9 + .../doctype/guardian_student/__init__.py | 0 .../guardian_student/guardian_student.json | 132 +++ .../guardian_student/guardian_student.py | 9 + .../education/doctype/instructor/__init__.py | 0 .../doctype/instructor/instructor.js | 64 ++ .../doctype/instructor/instructor.json | 125 +++ .../doctype/instructor/instructor.py | 54 ++ .../instructor/instructor_dashboard.py | 20 + .../doctype/instructor/test_instructor.py | 10 + .../doctype/instructor/test_records.json | 12 + .../doctype/instructor_log/__init__.py | 0 .../instructor_log/instructor_log.json | 88 ++ .../doctype/instructor_log/instructor_log.py | 9 + .../education/doctype/options/__init__.py | 0 .../education/doctype/options/options.json | 107 +++ .../education/doctype/options/options.py | 9 + .../education/doctype/program/__init__.py | 0 .../education/doctype/program/program.js | 8 + .../education/doctype/program/program.json | 177 ++++ .../education/doctype/program/program.py | 16 + .../doctype/program/program_dashboard.py | 16 + .../education/doctype/program/test_program.py | 103 +++ .../doctype/program/test_records.json | 12 + .../doctype/program_course/__init__.py | 0 .../program_course/program_course.json | 50 ++ .../doctype/program_course/program_course.py | 9 + .../doctype/program_enrollment/__init__.py | 0 .../program_enrollment/program_enrollment.js | 104 +++ .../program_enrollment.json | 218 +++++ .../program_enrollment/program_enrollment.py | 227 ++++++ .../program_enrollment_dashboard.py | 13 + .../test_program_enrollment.py | 42 + .../program_enrollment_course/__init__.py | 0 .../program_enrollment_course.json | 107 +++ .../program_enrollment_course.py | 9 + .../program_enrollment_fee/__init__.py | 0 .../program_enrollment_fee.json | 192 +++++ .../program_enrollment_fee.py | 9 + .../program_enrollment_tool/__init__.py | 0 .../program_enrollment_tool.js | 51 ++ .../program_enrollment_tool.json | 553 +++++++++++++ .../program_enrollment_tool.py | 99 +++ .../test_program_enrollment_tool.py | 8 + .../__init__.py | 0 .../program_enrollment_tool_student.json | 68 ++ .../program_enrollment_tool_student.py | 9 + .../education/doctype/program_fee/__init__.py | 0 .../doctype/program_fee/program_fee.json | 224 +++++ .../doctype/program_fee/program_fee.py | 9 + .../education/doctype/question/__init__.py | 0 .../education/doctype/question/question.js | 8 + .../education/doctype/question/question.json | 80 ++ .../education/doctype/question/question.py | 45 ++ .../doctype/question/test_question.py | 8 + education/education/doctype/quiz/__init__.py | 0 education/education/doctype/quiz/quiz.js | 71 ++ education/education/doctype/quiz/quiz.json | 128 +++ education/education/doctype/quiz/quiz.py | 79 ++ education/education/doctype/quiz/test_quiz.py | 8 + .../doctype/quiz_activity/__init__.py | 0 .../doctype/quiz_activity/quiz_activity.js | 8 + .../doctype/quiz_activity/quiz_activity.json | 163 ++++ .../doctype/quiz_activity/quiz_activity.py | 9 + .../quiz_activity/test_quiz_activity.py | 8 + .../doctype/quiz_question/__init__.py | 0 .../doctype/quiz_question/quiz_question.json | 40 + .../doctype/quiz_question/quiz_question.py | 9 + .../education/doctype/quiz_result/__init__.py | 0 .../doctype/quiz_result/quiz_result.js | 8 + .../doctype/quiz_result/quiz_result.json | 52 ++ .../doctype/quiz_result/quiz_result.py | 9 + .../doctype/quiz_result/test_quiz_result.py | 8 + education/education/doctype/room/__init__.py | 0 education/education/doctype/room/room.js | 2 + education/education/doctype/room/room.json | 161 ++++ education/education/doctype/room/room.py | 9 + .../education/doctype/room/room_dashboard.py | 14 + .../education/doctype/room/test_records.json | 17 + education/education/doctype/room/test_room.py | 10 + .../doctype/school_house/__init__.py | 0 .../doctype/school_house/school_house.js | 8 + .../doctype/school_house/school_house.json | 94 +++ .../doctype/school_house/school_house.py | 9 + .../doctype/school_house/test_school_house.py | 8 + .../education/doctype/student/__init__.py | 0 .../education/doctype/student/student.js | 63 ++ .../education/doctype/student/student.json | 304 +++++++ .../education/doctype/student/student.py | 232 ++++++ .../doctype/student/student_dashboard.py | 27 + .../education/doctype/student/student_list.js | 3 + .../doctype/student/test_records.json | 35 + .../education/doctype/student/test_student.py | 85 ++ .../doctype/student_admission/__init__.py | 0 .../student_admission/student_admission.js | 22 + .../student_admission/student_admission.json | 122 +++ .../student_admission/student_admission.py | 59 ++ .../templates/student_admission.html | 77 ++ .../templates/student_admission_row.html | 44 + .../test_student_admission.py | 10 + .../student_admission_program/__init__.py | 0 .../student_admission_program.json | 73 ++ .../student_admission_program.py | 9 + .../doctype/student_applicant/__init__.py | 0 .../student_applicant/student_applicant.js | 62 ++ .../student_applicant/student_applicant.json | 300 +++++++ .../student_applicant/student_applicant.py | 108 +++ .../student_applicant_list.js | 21 + .../test_student_applicant.py | 10 + .../doctype/student_attendance/__init__.py | 0 .../student_attendance/student_attendance.js | 5 + .../student_attendance.json | 134 +++ .../student_attendance/student_attendance.py | 142 ++++ .../student_attendance_dashboard.py | 12 + .../student_attendance_list.js | 11 + .../test_student_attendance.py | 10 + .../student_attendance_tool/__init__.py | 0 .../student_attendance_tool.js | 196 +++++ .../student_attendance_tool.json | 118 +++ .../student_attendance_tool.py | 67 ++ .../test_student_attendance_tool.py | 8 + .../doctype/student_batch_name/__init__.py | 0 .../student_batch_name/student_batch_name.js | 8 + .../student_batch_name.json | 94 +++ .../student_batch_name/student_batch_name.py | 9 + .../student_batch_name/test_records.json | 8 + .../test_student_batch_name.py | 10 + .../doctype/student_category/__init__.py | 0 .../student_category/student_category.js | 8 + .../student_category/student_category.json | 93 +++ .../student_category/student_category.py | 9 + .../student_category_dashboard.py | 10 + .../student_category/test_student_category.py | 10 + .../doctype/student_group/__init__.py | 0 .../doctype/student_group/student_group.js | 145 ++++ .../doctype/student_group/student_group.json | 161 ++++ .../doctype/student_group/student_group.py | 213 +++++ .../student_group/student_group_dashboard.py | 14 + .../doctype/student_group/test_records.json | 34 + .../student_group/test_student_group.py | 52 ++ .../student_group_creation_tool/__init__.py | 0 .../student_group_creation_tool.js | 40 + .../student_group_creation_tool.json | 309 +++++++ .../student_group_creation_tool.py | 111 +++ .../test_student_group_creation_tool.py | 8 + .../__init__.py | 0 .../student_group_creation_tool_course.json | 272 +++++++ .../student_group_creation_tool_course.py | 9 + .../student_group_instructor/__init__.py | 0 .../student_group_instructor.json | 142 ++++ .../student_group_instructor.py | 9 + .../doctype/student_group_student/__init__.py | 0 .../student_group_student.json | 204 +++++ .../student_group_student.py | 9 + .../doctype/student_guardian/__init__.py | 0 .../student_guardian/student_guardian.json | 141 ++++ .../student_guardian/student_guardian.py | 9 + .../doctype/student_language/__init__.py | 0 .../student_language/student_language.js | 8 + .../student_language/student_language.json | 98 +++ .../student_language/student_language.py | 9 + .../student_language/test_student_language.py | 10 + .../student_leave_application/__init__.py | 0 .../student_leave_application.js | 8 + .../student_leave_application.json | 165 ++++ .../student_leave_application.py | 139 ++++ .../student_leave_application_dashboard.py | 5 + .../test_student_leave_application.py | 139 ++++ .../education/doctype/student_log/__init__.py | 0 .../doctype/student_log/student_log.js | 8 + .../doctype/student_log/student_log.json | 423 ++++++++++ .../doctype/student_log/student_log.py | 9 + .../doctype/student_log/test_student_log.py | 10 + .../__init__.py | 0 .../student_report_generation_tool.html | 371 +++++++++ .../student_report_generation_tool.js | 66 ++ .../student_report_generation_tool.json | 618 ++++++++++++++ .../student_report_generation_tool.py | 125 +++ .../test_student_report_generation_tool.py | 8 + .../doctype/student_sibling/__init__.py | 0 .../student_sibling/student_sibling.json | 306 +++++++ .../student_sibling/student_sibling.py | 9 + .../doctype/student_siblings/__init__.py | 0 .../student_siblings/student_siblings.json | 141 ++++ .../student_siblings/student_siblings.py | 9 + education/education/doctype/topic/__init__.py | 0 .../education/doctype/topic/test_topic.py | 59 ++ education/education/doctype/topic/topic.js | 55 ++ education/education/doctype/topic/topic.json | 90 +++ education/education/doctype/topic/topic.py | 76 ++ .../doctype/topic_content/__init__.py | 0 .../topic_content/test_topic_content.py | 8 + .../doctype/topic_content/topic_content.js | 8 + .../doctype/topic_content/topic_content.json | 44 + .../doctype/topic_content/topic_content.py | 9 + education/education/report/__init__.py | 0 .../report/absent_student_report/__init__.py | 0 .../absent_student_report.js | 15 + .../absent_student_report.json | 24 + .../absent_student_report.py | 137 ++++ .../report/assessment_plan_status/__init__.py | 0 .../assessment_plan_status.js | 28 + .../assessment_plan_status.json | 24 + .../assessment_plan_status.py | 200 +++++ .../course_wise_assessment_report/__init__.py | 0 .../course_wise_assessment_report.html | 50 ++ .../course_wise_assessment_report.js | 40 + .../course_wise_assessment_report.json | 28 + .../course_wise_assessment_report.py | 322 ++++++++ .../final_assessment_grades/__init__.py | 0 .../final_assessment_grades.js | 38 + .../final_assessment_grades.json | 27 + .../final_assessment_grades.py | 99 +++ .../program_wise_fee_collection/__init__.py | 0 .../program_wise_fee_collection.js | 22 + .../program_wise_fee_collection.json | 32 + .../program_wise_fee_collection.py | 127 +++ .../__init__.py | 0 .../student_and_guardian_contact_details.js | 29 + .../student_and_guardian_contact_details.json | 27 + .../student_and_guardian_contact_details.py | 156 ++++ .../student_batch_wise_attendance/__init__.py | 0 .../student_batch_wise_attendance.js | 12 + .../student_batch_wise_attendance.json | 24 + .../student_batch_wise_attendance.py | 92 +++ .../report/student_fee_collection/__init__.py | 0 .../student_fee_collection.json | 25 + .../__init__.py | 0 .../student_monthly_attendance_sheet.js | 42 + .../student_monthly_attendance_sheet.json | 24 + .../student_monthly_attendance_sheet.py | 164 ++++ education/education/setup.py | 98 +++ education/education/utils.py | 476 +++++++++++ .../workspace/education/education.json | 765 ++++++++++++++++++ education/hooks.py | 283 +++++++ education/modules.txt | 1 + education/patches.txt | 0 education/public/.gitkeep | 0 .../public/js/assessment_result_tool.html | 72 ++ education/public/js/quiz.js | 238 ++++++ education/templates/__init__.py | 0 .../generators/student_admission.html | 27 + .../includes/assessment/assessment_row.html | 19 + .../templates/includes/course/course_row.html | 18 + .../templates/includes/course/macros.html | 1 + education/templates/pages/__init__.py | 0 education/www/__init__.py | 0 education/www/lms/__init__.py | 0 education/www/lms/content.html | 246 ++++++ education/www/lms/content.py | 78 ++ education/www/lms/course.html | 106 +++ education/www/lms/course.py | 30 + education/www/lms/index.html | 69 ++ education/www/lms/index.py | 17 + education/www/lms/macros/__init__.py | 0 education/www/lms/macros/card.html | 34 + education/www/lms/macros/hero.html | 53 ++ education/www/lms/profile.html | 64 ++ education/www/lms/profile.py | 37 + education/www/lms/program.html | 87 ++ education/www/lms/program.py | 36 + education/www/lms/topic.html | 58 ++ education/www/lms/topic.py | 61 ++ license.txt | 1 + requirements.txt | 1 + setup.py | 19 + 450 files changed, 26274 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/deployment.xml create mode 100644 .idea/education.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .pre-commit-config.yaml create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 education/__init__.py create mode 100644 education/config/__init__.py create mode 100644 education/config/desktop.py create mode 100644 education/config/docs.py create mode 100644 education/education/__init__.py create mode 100644 education/education/api.py create mode 100644 education/education/doctype/__init__.py create mode 100644 education/education/doctype/academic_term/__init__.py create mode 100644 education/education/doctype/academic_term/academic_term.js create mode 100644 education/education/doctype/academic_term/academic_term.json create mode 100644 education/education/doctype/academic_term/academic_term.py create mode 100644 education/education/doctype/academic_term/academic_term_dashboard.py create mode 100644 education/education/doctype/academic_term/test_academic_term.py create mode 100644 education/education/doctype/academic_term/test_records.json create mode 100644 education/education/doctype/academic_year/__init__.py create mode 100644 education/education/doctype/academic_year/academic_year.js create mode 100644 education/education/doctype/academic_year/academic_year.json create mode 100644 education/education/doctype/academic_year/academic_year.py create mode 100644 education/education/doctype/academic_year/academic_year_dashboard.py create mode 100644 education/education/doctype/academic_year/test_academic_year.py create mode 100644 education/education/doctype/academic_year/test_records.json create mode 100644 education/education/doctype/article/__init__.py create mode 100644 education/education/doctype/article/article.js create mode 100644 education/education/doctype/article/article.json create mode 100644 education/education/doctype/article/article.py create mode 100644 education/education/doctype/article/test_article.py create mode 100644 education/education/doctype/assessment_criteria/__init__.py create mode 100644 education/education/doctype/assessment_criteria/assessment_criteria.js create mode 100644 education/education/doctype/assessment_criteria/assessment_criteria.json create mode 100644 education/education/doctype/assessment_criteria/assessment_criteria.py create mode 100644 education/education/doctype/assessment_criteria/test_assessment_criteria.py create mode 100644 education/education/doctype/assessment_criteria/test_records.json create mode 100644 education/education/doctype/assessment_criteria_group/__init__.py create mode 100644 education/education/doctype/assessment_criteria_group/assessment_criteria_group.js create mode 100644 education/education/doctype/assessment_criteria_group/assessment_criteria_group.json create mode 100644 education/education/doctype/assessment_criteria_group/assessment_criteria_group.py create mode 100644 education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py create mode 100644 education/education/doctype/assessment_group/__init__.py create mode 100644 education/education/doctype/assessment_group/assessment_group.js create mode 100644 education/education/doctype/assessment_group/assessment_group.json create mode 100644 education/education/doctype/assessment_group/assessment_group.py create mode 100644 education/education/doctype/assessment_group/assessment_group_dashboard.py create mode 100644 education/education/doctype/assessment_group/assessment_group_tree.js create mode 100644 education/education/doctype/assessment_group/test_assessment_group.py create mode 100644 education/education/doctype/assessment_plan/__init__.py create mode 100644 education/education/doctype/assessment_plan/assessment_plan.js create mode 100644 education/education/doctype/assessment_plan/assessment_plan.json create mode 100644 education/education/doctype/assessment_plan/assessment_plan.py create mode 100644 education/education/doctype/assessment_plan/assessment_plan_dashboard.py create mode 100644 education/education/doctype/assessment_plan/test_assessment_plan.py create mode 100644 education/education/doctype/assessment_plan_criteria/__init__.py create mode 100644 education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.json create mode 100644 education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.py create mode 100644 education/education/doctype/assessment_result/__init__.py create mode 100644 education/education/doctype/assessment_result/assessment_result.js create mode 100644 education/education/doctype/assessment_result/assessment_result.json create mode 100644 education/education/doctype/assessment_result/assessment_result.py create mode 100644 education/education/doctype/assessment_result/assessment_result_dashboard.py create mode 100644 education/education/doctype/assessment_result/test_assessment_result.py create mode 100644 education/education/doctype/assessment_result_detail/__init__.py create mode 100644 education/education/doctype/assessment_result_detail/assessment_result_detail.json create mode 100644 education/education/doctype/assessment_result_detail/assessment_result_detail.py create mode 100644 education/education/doctype/assessment_result_tool/__init__.py create mode 100644 education/education/doctype/assessment_result_tool/assessment_result_tool.js create mode 100644 education/education/doctype/assessment_result_tool/assessment_result_tool.json create mode 100644 education/education/doctype/assessment_result_tool/assessment_result_tool.py create mode 100644 education/education/doctype/assessment_result_tool/test_assessment_result_tool.py create mode 100644 education/education/doctype/content_activity/__init__.py create mode 100644 education/education/doctype/content_activity/content_activity.json create mode 100644 education/education/doctype/content_activity/content_activity.py create mode 100644 education/education/doctype/content_question/__init__.py create mode 100644 education/education/doctype/content_question/content_question.js create mode 100644 education/education/doctype/content_question/content_question.json create mode 100644 education/education/doctype/content_question/content_question.py create mode 100644 education/education/doctype/content_question/test_content_question.py create mode 100644 education/education/doctype/course/__init__.py create mode 100644 education/education/doctype/course/course.js create mode 100644 education/education/doctype/course/course.json create mode 100644 education/education/doctype/course/course.py create mode 100644 education/education/doctype/course/course_dashboard.py create mode 100644 education/education/doctype/course/test_course.py create mode 100644 education/education/doctype/course/test_records.json create mode 100644 education/education/doctype/course_activity/__init__.py create mode 100644 education/education/doctype/course_activity/course_activity.js create mode 100644 education/education/doctype/course_activity/course_activity.json create mode 100644 education/education/doctype/course_activity/course_activity.py create mode 100644 education/education/doctype/course_activity/test_course_activity.py create mode 100644 education/education/doctype/course_assessment_criteria/__init__.py create mode 100644 education/education/doctype/course_assessment_criteria/course_assessment_criteria.json create mode 100644 education/education/doctype/course_assessment_criteria/course_assessment_criteria.py create mode 100644 education/education/doctype/course_content/__init__.py create mode 100644 education/education/doctype/course_content/course_content.js create mode 100644 education/education/doctype/course_content/course_content.json create mode 100644 education/education/doctype/course_content/course_content.py create mode 100644 education/education/doctype/course_content/test_course_content.py create mode 100644 education/education/doctype/course_enrollment/__init__.py create mode 100644 education/education/doctype/course_enrollment/course_enrollment.js create mode 100644 education/education/doctype/course_enrollment/course_enrollment.json create mode 100644 education/education/doctype/course_enrollment/course_enrollment.py create mode 100644 education/education/doctype/course_enrollment/course_enrollment_dashboard.py create mode 100644 education/education/doctype/course_enrollment/test_course_enrollment.py create mode 100644 education/education/doctype/course_schedule/__init__.py create mode 100644 education/education/doctype/course_schedule/course_schedule.js create mode 100644 education/education/doctype/course_schedule/course_schedule.json create mode 100644 education/education/doctype/course_schedule/course_schedule.py create mode 100644 education/education/doctype/course_schedule/course_schedule_calendar.js create mode 100644 education/education/doctype/course_schedule/course_schedule_dashboard.py create mode 100644 education/education/doctype/course_schedule/test_course_schedule.py create mode 100644 education/education/doctype/course_scheduling_tool/__init__.py create mode 100644 education/education/doctype/course_scheduling_tool/course_scheduling_tool.js create mode 100644 education/education/doctype/course_scheduling_tool/course_scheduling_tool.json create mode 100644 education/education/doctype/course_scheduling_tool/course_scheduling_tool.py create mode 100644 education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py create mode 100644 education/education/doctype/course_topic/__init__.py create mode 100644 education/education/doctype/course_topic/course_topic.js create mode 100644 education/education/doctype/course_topic/course_topic.json create mode 100644 education/education/doctype/course_topic/course_topic.py create mode 100644 education/education/doctype/course_topic/test_course_topic.py create mode 100644 education/education/doctype/education_settings/__init__.py create mode 100644 education/education/doctype/education_settings/education_settings.js create mode 100644 education/education/doctype/education_settings/education_settings.json create mode 100644 education/education/doctype/education_settings/education_settings.py create mode 100644 education/education/doctype/education_settings/test_education_settings.py create mode 100644 education/education/doctype/fee_category/__init__.py create mode 100644 education/education/doctype/fee_category/fee_category.js create mode 100644 education/education/doctype/fee_category/fee_category.json create mode 100644 education/education/doctype/fee_category/fee_category.py create mode 100644 education/education/doctype/fee_category/test_fee_category.py create mode 100644 education/education/doctype/fee_category/test_records.json create mode 100644 education/education/doctype/fee_component/__init__.py create mode 100644 education/education/doctype/fee_component/fee_component.json create mode 100644 education/education/doctype/fee_component/fee_component.py create mode 100644 education/education/doctype/fee_schedule/__init__.py create mode 100644 education/education/doctype/fee_schedule/fee_schedule.js create mode 100644 education/education/doctype/fee_schedule/fee_schedule.json create mode 100644 education/education/doctype/fee_schedule/fee_schedule.py create mode 100644 education/education/doctype/fee_schedule/fee_schedule_dashboard.py create mode 100644 education/education/doctype/fee_schedule/fee_schedule_list.js create mode 100644 education/education/doctype/fee_schedule/test_fee_schedule.py create mode 100644 education/education/doctype/fee_schedule_program/__init__.py create mode 100644 education/education/doctype/fee_schedule_program/fee_schedule_program.json create mode 100644 education/education/doctype/fee_schedule_program/fee_schedule_program.py create mode 100644 education/education/doctype/fee_schedule_student_group/__init__.py create mode 100644 education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.json create mode 100644 education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.py create mode 100644 education/education/doctype/fee_structure/__init__.py create mode 100644 education/education/doctype/fee_structure/fee_structure.js create mode 100644 education/education/doctype/fee_structure/fee_structure.json create mode 100644 education/education/doctype/fee_structure/fee_structure.py create mode 100644 education/education/doctype/fee_structure/fee_structure_dashboard.py create mode 100644 education/education/doctype/fee_structure/test_fee_structure.py create mode 100644 education/education/doctype/fee_structure/test_records.json create mode 100644 education/education/doctype/fees/__init__.py create mode 100644 education/education/doctype/fees/fees.js create mode 100644 education/education/doctype/fees/fees.json create mode 100644 education/education/doctype/fees/fees.py create mode 100644 education/education/doctype/fees/fees_list.js create mode 100644 education/education/doctype/fees/test_fees.py create mode 100644 education/education/doctype/grading_scale/__init__.py create mode 100644 education/education/doctype/grading_scale/grading_scale.js create mode 100644 education/education/doctype/grading_scale/grading_scale.json create mode 100644 education/education/doctype/grading_scale/grading_scale.py create mode 100644 education/education/doctype/grading_scale/grading_scale_dashboard.py create mode 100644 education/education/doctype/grading_scale/test_grading_scale.py create mode 100644 education/education/doctype/grading_scale/test_records.json create mode 100644 education/education/doctype/grading_scale_interval/__init__.py create mode 100644 education/education/doctype/grading_scale_interval/grading_scale_interval.json create mode 100644 education/education/doctype/grading_scale_interval/grading_scale_interval.py create mode 100644 education/education/doctype/guardian/__init__.py create mode 100644 education/education/doctype/guardian/guardian.js create mode 100644 education/education/doctype/guardian/guardian.json create mode 100644 education/education/doctype/guardian/guardian.py create mode 100644 education/education/doctype/guardian/test_guardian.py create mode 100644 education/education/doctype/guardian_interest/__init__.py create mode 100644 education/education/doctype/guardian_interest/guardian_interest.json create mode 100644 education/education/doctype/guardian_interest/guardian_interest.py create mode 100644 education/education/doctype/guardian_student/__init__.py create mode 100644 education/education/doctype/guardian_student/guardian_student.json create mode 100644 education/education/doctype/guardian_student/guardian_student.py create mode 100644 education/education/doctype/instructor/__init__.py create mode 100644 education/education/doctype/instructor/instructor.js create mode 100644 education/education/doctype/instructor/instructor.json create mode 100644 education/education/doctype/instructor/instructor.py create mode 100644 education/education/doctype/instructor/instructor_dashboard.py create mode 100644 education/education/doctype/instructor/test_instructor.py create mode 100644 education/education/doctype/instructor/test_records.json create mode 100644 education/education/doctype/instructor_log/__init__.py create mode 100644 education/education/doctype/instructor_log/instructor_log.json create mode 100644 education/education/doctype/instructor_log/instructor_log.py create mode 100644 education/education/doctype/options/__init__.py create mode 100644 education/education/doctype/options/options.json create mode 100644 education/education/doctype/options/options.py create mode 100644 education/education/doctype/program/__init__.py create mode 100644 education/education/doctype/program/program.js create mode 100644 education/education/doctype/program/program.json create mode 100644 education/education/doctype/program/program.py create mode 100644 education/education/doctype/program/program_dashboard.py create mode 100644 education/education/doctype/program/test_program.py create mode 100644 education/education/doctype/program/test_records.json create mode 100644 education/education/doctype/program_course/__init__.py create mode 100644 education/education/doctype/program_course/program_course.json create mode 100644 education/education/doctype/program_course/program_course.py create mode 100644 education/education/doctype/program_enrollment/__init__.py create mode 100644 education/education/doctype/program_enrollment/program_enrollment.js create mode 100644 education/education/doctype/program_enrollment/program_enrollment.json create mode 100644 education/education/doctype/program_enrollment/program_enrollment.py create mode 100644 education/education/doctype/program_enrollment/program_enrollment_dashboard.py create mode 100644 education/education/doctype/program_enrollment/test_program_enrollment.py create mode 100644 education/education/doctype/program_enrollment_course/__init__.py create mode 100644 education/education/doctype/program_enrollment_course/program_enrollment_course.json create mode 100644 education/education/doctype/program_enrollment_course/program_enrollment_course.py create mode 100644 education/education/doctype/program_enrollment_fee/__init__.py create mode 100644 education/education/doctype/program_enrollment_fee/program_enrollment_fee.json create mode 100644 education/education/doctype/program_enrollment_fee/program_enrollment_fee.py create mode 100644 education/education/doctype/program_enrollment_tool/__init__.py create mode 100644 education/education/doctype/program_enrollment_tool/program_enrollment_tool.js create mode 100644 education/education/doctype/program_enrollment_tool/program_enrollment_tool.json create mode 100644 education/education/doctype/program_enrollment_tool/program_enrollment_tool.py create mode 100644 education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py create mode 100644 education/education/doctype/program_enrollment_tool_student/__init__.py create mode 100644 education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json create mode 100644 education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.py create mode 100644 education/education/doctype/program_fee/__init__.py create mode 100644 education/education/doctype/program_fee/program_fee.json create mode 100644 education/education/doctype/program_fee/program_fee.py create mode 100644 education/education/doctype/question/__init__.py create mode 100644 education/education/doctype/question/question.js create mode 100644 education/education/doctype/question/question.json create mode 100644 education/education/doctype/question/question.py create mode 100644 education/education/doctype/question/test_question.py create mode 100644 education/education/doctype/quiz/__init__.py create mode 100644 education/education/doctype/quiz/quiz.js create mode 100644 education/education/doctype/quiz/quiz.json create mode 100644 education/education/doctype/quiz/quiz.py create mode 100644 education/education/doctype/quiz/test_quiz.py create mode 100644 education/education/doctype/quiz_activity/__init__.py create mode 100644 education/education/doctype/quiz_activity/quiz_activity.js create mode 100644 education/education/doctype/quiz_activity/quiz_activity.json create mode 100644 education/education/doctype/quiz_activity/quiz_activity.py create mode 100644 education/education/doctype/quiz_activity/test_quiz_activity.py create mode 100644 education/education/doctype/quiz_question/__init__.py create mode 100644 education/education/doctype/quiz_question/quiz_question.json create mode 100644 education/education/doctype/quiz_question/quiz_question.py create mode 100644 education/education/doctype/quiz_result/__init__.py create mode 100644 education/education/doctype/quiz_result/quiz_result.js create mode 100644 education/education/doctype/quiz_result/quiz_result.json create mode 100644 education/education/doctype/quiz_result/quiz_result.py create mode 100644 education/education/doctype/quiz_result/test_quiz_result.py create mode 100644 education/education/doctype/room/__init__.py create mode 100644 education/education/doctype/room/room.js create mode 100644 education/education/doctype/room/room.json create mode 100644 education/education/doctype/room/room.py create mode 100644 education/education/doctype/room/room_dashboard.py create mode 100644 education/education/doctype/room/test_records.json create mode 100644 education/education/doctype/room/test_room.py create mode 100644 education/education/doctype/school_house/__init__.py create mode 100644 education/education/doctype/school_house/school_house.js create mode 100644 education/education/doctype/school_house/school_house.json create mode 100644 education/education/doctype/school_house/school_house.py create mode 100644 education/education/doctype/school_house/test_school_house.py create mode 100644 education/education/doctype/student/__init__.py create mode 100644 education/education/doctype/student/student.js create mode 100644 education/education/doctype/student/student.json create mode 100644 education/education/doctype/student/student.py create mode 100644 education/education/doctype/student/student_dashboard.py create mode 100644 education/education/doctype/student/student_list.js create mode 100644 education/education/doctype/student/test_records.json create mode 100644 education/education/doctype/student/test_student.py create mode 100644 education/education/doctype/student_admission/__init__.py create mode 100644 education/education/doctype/student_admission/student_admission.js create mode 100644 education/education/doctype/student_admission/student_admission.json create mode 100644 education/education/doctype/student_admission/student_admission.py create mode 100644 education/education/doctype/student_admission/templates/student_admission.html create mode 100644 education/education/doctype/student_admission/templates/student_admission_row.html create mode 100644 education/education/doctype/student_admission/test_student_admission.py create mode 100644 education/education/doctype/student_admission_program/__init__.py create mode 100644 education/education/doctype/student_admission_program/student_admission_program.json create mode 100644 education/education/doctype/student_admission_program/student_admission_program.py create mode 100644 education/education/doctype/student_applicant/__init__.py create mode 100644 education/education/doctype/student_applicant/student_applicant.js create mode 100644 education/education/doctype/student_applicant/student_applicant.json create mode 100644 education/education/doctype/student_applicant/student_applicant.py create mode 100644 education/education/doctype/student_applicant/student_applicant_list.js create mode 100644 education/education/doctype/student_applicant/test_student_applicant.py create mode 100644 education/education/doctype/student_attendance/__init__.py create mode 100644 education/education/doctype/student_attendance/student_attendance.js create mode 100644 education/education/doctype/student_attendance/student_attendance.json create mode 100644 education/education/doctype/student_attendance/student_attendance.py create mode 100644 education/education/doctype/student_attendance/student_attendance_dashboard.py create mode 100644 education/education/doctype/student_attendance/student_attendance_list.js create mode 100644 education/education/doctype/student_attendance/test_student_attendance.py create mode 100644 education/education/doctype/student_attendance_tool/__init__.py create mode 100644 education/education/doctype/student_attendance_tool/student_attendance_tool.js create mode 100644 education/education/doctype/student_attendance_tool/student_attendance_tool.json create mode 100644 education/education/doctype/student_attendance_tool/student_attendance_tool.py create mode 100644 education/education/doctype/student_attendance_tool/test_student_attendance_tool.py create mode 100644 education/education/doctype/student_batch_name/__init__.py create mode 100644 education/education/doctype/student_batch_name/student_batch_name.js create mode 100644 education/education/doctype/student_batch_name/student_batch_name.json create mode 100644 education/education/doctype/student_batch_name/student_batch_name.py create mode 100644 education/education/doctype/student_batch_name/test_records.json create mode 100644 education/education/doctype/student_batch_name/test_student_batch_name.py create mode 100644 education/education/doctype/student_category/__init__.py create mode 100644 education/education/doctype/student_category/student_category.js create mode 100644 education/education/doctype/student_category/student_category.json create mode 100644 education/education/doctype/student_category/student_category.py create mode 100644 education/education/doctype/student_category/student_category_dashboard.py create mode 100644 education/education/doctype/student_category/test_student_category.py create mode 100644 education/education/doctype/student_group/__init__.py create mode 100644 education/education/doctype/student_group/student_group.js create mode 100644 education/education/doctype/student_group/student_group.json create mode 100644 education/education/doctype/student_group/student_group.py create mode 100644 education/education/doctype/student_group/student_group_dashboard.py create mode 100644 education/education/doctype/student_group/test_records.json create mode 100644 education/education/doctype/student_group/test_student_group.py create mode 100644 education/education/doctype/student_group_creation_tool/__init__.py create mode 100644 education/education/doctype/student_group_creation_tool/student_group_creation_tool.js create mode 100644 education/education/doctype/student_group_creation_tool/student_group_creation_tool.json create mode 100644 education/education/doctype/student_group_creation_tool/student_group_creation_tool.py create mode 100644 education/education/doctype/student_group_creation_tool/test_student_group_creation_tool.py create mode 100644 education/education/doctype/student_group_creation_tool_course/__init__.py create mode 100644 education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.json create mode 100644 education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.py create mode 100644 education/education/doctype/student_group_instructor/__init__.py create mode 100644 education/education/doctype/student_group_instructor/student_group_instructor.json create mode 100644 education/education/doctype/student_group_instructor/student_group_instructor.py create mode 100644 education/education/doctype/student_group_student/__init__.py create mode 100644 education/education/doctype/student_group_student/student_group_student.json create mode 100644 education/education/doctype/student_group_student/student_group_student.py create mode 100644 education/education/doctype/student_guardian/__init__.py create mode 100644 education/education/doctype/student_guardian/student_guardian.json create mode 100644 education/education/doctype/student_guardian/student_guardian.py create mode 100644 education/education/doctype/student_language/__init__.py create mode 100644 education/education/doctype/student_language/student_language.js create mode 100644 education/education/doctype/student_language/student_language.json create mode 100644 education/education/doctype/student_language/student_language.py create mode 100644 education/education/doctype/student_language/test_student_language.py create mode 100644 education/education/doctype/student_leave_application/__init__.py create mode 100644 education/education/doctype/student_leave_application/student_leave_application.js create mode 100644 education/education/doctype/student_leave_application/student_leave_application.json create mode 100644 education/education/doctype/student_leave_application/student_leave_application.py create mode 100644 education/education/doctype/student_leave_application/student_leave_application_dashboard.py create mode 100644 education/education/doctype/student_leave_application/test_student_leave_application.py create mode 100644 education/education/doctype/student_log/__init__.py create mode 100644 education/education/doctype/student_log/student_log.js create mode 100644 education/education/doctype/student_log/student_log.json create mode 100644 education/education/doctype/student_log/student_log.py create mode 100644 education/education/doctype/student_log/test_student_log.py create mode 100644 education/education/doctype/student_report_generation_tool/__init__.py create mode 100644 education/education/doctype/student_report_generation_tool/student_report_generation_tool.html create mode 100644 education/education/doctype/student_report_generation_tool/student_report_generation_tool.js create mode 100644 education/education/doctype/student_report_generation_tool/student_report_generation_tool.json create mode 100644 education/education/doctype/student_report_generation_tool/student_report_generation_tool.py create mode 100644 education/education/doctype/student_report_generation_tool/test_student_report_generation_tool.py create mode 100644 education/education/doctype/student_sibling/__init__.py create mode 100644 education/education/doctype/student_sibling/student_sibling.json create mode 100644 education/education/doctype/student_sibling/student_sibling.py create mode 100644 education/education/doctype/student_siblings/__init__.py create mode 100644 education/education/doctype/student_siblings/student_siblings.json create mode 100644 education/education/doctype/student_siblings/student_siblings.py create mode 100644 education/education/doctype/topic/__init__.py create mode 100644 education/education/doctype/topic/test_topic.py create mode 100644 education/education/doctype/topic/topic.js create mode 100644 education/education/doctype/topic/topic.json create mode 100644 education/education/doctype/topic/topic.py create mode 100644 education/education/doctype/topic_content/__init__.py create mode 100644 education/education/doctype/topic_content/test_topic_content.py create mode 100644 education/education/doctype/topic_content/topic_content.js create mode 100644 education/education/doctype/topic_content/topic_content.json create mode 100644 education/education/doctype/topic_content/topic_content.py create mode 100644 education/education/report/__init__.py create mode 100644 education/education/report/absent_student_report/__init__.py create mode 100644 education/education/report/absent_student_report/absent_student_report.js create mode 100644 education/education/report/absent_student_report/absent_student_report.json create mode 100644 education/education/report/absent_student_report/absent_student_report.py create mode 100644 education/education/report/assessment_plan_status/__init__.py create mode 100644 education/education/report/assessment_plan_status/assessment_plan_status.js create mode 100644 education/education/report/assessment_plan_status/assessment_plan_status.json create mode 100644 education/education/report/assessment_plan_status/assessment_plan_status.py create mode 100644 education/education/report/course_wise_assessment_report/__init__.py create mode 100644 education/education/report/course_wise_assessment_report/course_wise_assessment_report.html create mode 100644 education/education/report/course_wise_assessment_report/course_wise_assessment_report.js create mode 100644 education/education/report/course_wise_assessment_report/course_wise_assessment_report.json create mode 100644 education/education/report/course_wise_assessment_report/course_wise_assessment_report.py create mode 100644 education/education/report/final_assessment_grades/__init__.py create mode 100644 education/education/report/final_assessment_grades/final_assessment_grades.js create mode 100644 education/education/report/final_assessment_grades/final_assessment_grades.json create mode 100644 education/education/report/final_assessment_grades/final_assessment_grades.py create mode 100644 education/education/report/program_wise_fee_collection/__init__.py create mode 100644 education/education/report/program_wise_fee_collection/program_wise_fee_collection.js create mode 100644 education/education/report/program_wise_fee_collection/program_wise_fee_collection.json create mode 100644 education/education/report/program_wise_fee_collection/program_wise_fee_collection.py create mode 100644 education/education/report/student_and_guardian_contact_details/__init__.py create mode 100644 education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.js create mode 100644 education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json create mode 100644 education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py create mode 100644 education/education/report/student_batch_wise_attendance/__init__.py create mode 100644 education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js create mode 100644 education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json create mode 100644 education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py create mode 100644 education/education/report/student_fee_collection/__init__.py create mode 100644 education/education/report/student_fee_collection/student_fee_collection.json create mode 100644 education/education/report/student_monthly_attendance_sheet/__init__.py create mode 100644 education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js create mode 100644 education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json create mode 100644 education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py create mode 100644 education/education/setup.py create mode 100644 education/education/utils.py create mode 100644 education/education/workspace/education/education.json create mode 100644 education/hooks.py create mode 100644 education/modules.txt create mode 100644 education/patches.txt create mode 100644 education/public/.gitkeep create mode 100644 education/public/js/assessment_result_tool.html create mode 100644 education/public/js/quiz.js create mode 100644 education/templates/__init__.py create mode 100644 education/templates/generators/student_admission.html create mode 100644 education/templates/includes/assessment/assessment_row.html create mode 100644 education/templates/includes/course/course_row.html create mode 100644 education/templates/includes/course/macros.html create mode 100644 education/templates/pages/__init__.py create mode 100644 education/www/__init__.py create mode 100644 education/www/lms/__init__.py create mode 100644 education/www/lms/content.html create mode 100644 education/www/lms/content.py create mode 100644 education/www/lms/course.html create mode 100644 education/www/lms/course.py create mode 100644 education/www/lms/index.html create mode 100644 education/www/lms/index.py create mode 100644 education/www/lms/macros/__init__.py create mode 100644 education/www/lms/macros/card.html create mode 100644 education/www/lms/macros/hero.html create mode 100644 education/www/lms/profile.html create mode 100644 education/www/lms/profile.py create mode 100644 education/www/lms/program.html create mode 100644 education/www/lms/program.py create mode 100644 education/www/lms/topic.html create mode 100644 education/www/lms/topic.py create mode 100644 license.txt create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..faa81a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +*.pyc +*.egg-info +*.swp +tags +education/docs/current \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..89064f6 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/education.iml b/.idea/education.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/education.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..32e11c0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b39f1ca --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +exclude: 'node_modules|.git' +default_stages: [commit] +fail_fast: false + + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: trailing-whitespace + files: "frappe.*" + exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" + - id: check-yaml + - id: no-commit-to-branch + args: ['--branch', 'develop'] + - id: check-merge-conflict + - id: check-ast + + - repo: https://github.com/adityahase/black + rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 + hooks: + - id: black + additional_dependencies: ['click==8.0.4'] + + - repo: https://github.com/timothycrosley/isort + rev: 5.9.1 + hooks: + - id: isort + exclude: ".*setup.py$" + +ci: + autoupdate_schedule: weekly + skip: [] + submodules: false diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..27f7e09 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,18 @@ +include MANIFEST.in +include requirements.txt +include *.json +include *.md +include *.py +include *.txt +recursive-include education *.css +recursive-include education *.csv +recursive-include education *.html +recursive-include education *.ico +recursive-include education *.js +recursive-include education *.json +recursive-include education *.md +recursive-include education *.png +recursive-include education *.py +recursive-include education *.svg +recursive-include education *.txt +recursive-exclude education *.pyc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a3cbb0 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +## Education + +The Education Domain of InfluxERP comes with features to record student info and courses, track assessment, fees payments, curate quizes and programs. You can check out the following topics after this brief introduction to the education module. + + +### Installation + +Using bench, [install InfluxERP](https://github.com/frappe/bench#installation) as mentioned here. + +Once InfluxERP is installed, add education app to your bench by running +```sh +$ bench get-app education +``` + +After that, you can install education app on required site by running + +```sh +$ bench --site demo.com install-app education +``` + + +### License + +GNU GPL V3. See [license.txt](https://github.com/frappe/agriculture/blob/develop/license.txt) for more information. diff --git a/education/__init__.py b/education/__init__.py new file mode 100644 index 0000000..f102a9c --- /dev/null +++ b/education/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.1" diff --git a/education/config/__init__.py b/education/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/config/desktop.py b/education/config/desktop.py new file mode 100644 index 0000000..7a2d022 --- /dev/null +++ b/education/config/desktop.py @@ -0,0 +1,13 @@ +from frappe import _ + + +def get_data(): + return [ + { + "module_name": "Education", + "color": "grey", + "icon": "octicon octicon-file-directory", + "type": "module", + "label": _("Education"), + } + ] diff --git a/education/config/docs.py b/education/config/docs.py new file mode 100644 index 0000000..0fd3a96 --- /dev/null +++ b/education/config/docs.py @@ -0,0 +1,11 @@ +""" +Configuration for docs +""" + +# source_link = "https://github.com/[org_name]/education" +# headline = "App that does everything" +# sub_heading = "Yes, you got that right the first time, everything" + + +def get_context(context): + context.brand_html = "Education" diff --git a/education/education/__init__.py b/education/education/__init__.py new file mode 100644 index 0000000..ed256fa --- /dev/null +++ b/education/education/__init__.py @@ -0,0 +1,19 @@ +import frappe +from frappe import _ + + +class StudentNotInGroupError(frappe.ValidationError): + pass + + +def validate_student_belongs_to_group(student, student_group): + groups = frappe.db.get_all( + "Student Group Student", ["parent"], dict(student=student, active=1) + ) + if not student_group in [d.parent for d in groups]: + frappe.throw( + _("Student {0} does not belong to group {1}").format( + frappe.bold(student), frappe.bold(student_group) + ), + StudentNotInGroupError, + ) diff --git a/education/education/api.py b/education/education/api.py new file mode 100644 index 0000000..02b8756 --- /dev/null +++ b/education/education/api.py @@ -0,0 +1,483 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.email.doctype.email_group.email_group import add_subscribers +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cstr, flt, getdate + + +def get_course(program): + """Return list of courses for a particular program + :param program: Program + """ + courses = frappe.db.sql( + """select course, course_name from `tabProgram Course` where parent=%s""", + (program), + as_dict=1, + ) + return courses + + +@frappe.whitelist() +def enroll_student(source_name): + """Creates a Student Record and returns a Program Enrollment. + + :param source_name: Student Applicant. + """ + frappe.publish_realtime( + "enroll_student_progress", {"progress": [1, 4]}, user=frappe.session.user + ) + student = get_mapped_doc( + "Student Applicant", + source_name, + { + "Student Applicant": { + "doctype": "Student", + "field_map": {"name": "student_applicant"}, + } + }, + ignore_permissions=True, + ) + student.save() + + student_applicant = frappe.db.get_value( + "Student Applicant", source_name, ["student_category", "program"], as_dict=True + ) + program_enrollment = frappe.new_doc("Program Enrollment") + program_enrollment.student = student.name + program_enrollment.student_category = student_applicant.student_category + program_enrollment.student_name = student.title + program_enrollment.program = student_applicant.program + frappe.publish_realtime( + "enroll_student_progress", {"progress": [2, 4]}, user=frappe.session.user + ) + return program_enrollment + + +@frappe.whitelist() +def check_attendance_records_exist(course_schedule=None, student_group=None, date=None): + """Check if Attendance Records are made against the specified Course Schedule or Student Group for given date. + + :param course_schedule: Course Schedule. + :param student_group: Student Group. + :param date: Date. + """ + if course_schedule: + return frappe.get_list( + "Student Attendance", filters={"course_schedule": course_schedule} + ) + else: + return frappe.get_list( + "Student Attendance", filters={"student_group": student_group, "date": date} + ) + + +@frappe.whitelist() +def mark_attendance( + students_present, students_absent, course_schedule=None, student_group=None, date=None +): + """Creates Multiple Attendance Records. + + :param students_present: Students Present JSON. + :param students_absent: Students Absent JSON. + :param course_schedule: Course Schedule. + :param student_group: Student Group. + :param date: Date. + """ + + if student_group: + academic_year = frappe.db.get_value("Student Group", student_group, "academic_year") + if academic_year: + year_start_date, year_end_date = frappe.db.get_value( + "Academic Year", academic_year, ["year_start_date", "year_end_date"] + ) + if getdate(date) < getdate(year_start_date) or getdate(date) > getdate( + year_end_date + ): + frappe.throw( + _("Attendance cannot be marked outside of Academic Year {0}").format(academic_year) + ) + + present = json.loads(students_present) + absent = json.loads(students_absent) + + for d in present: + make_attendance_records( + d["student"], d["student_name"], "Present", course_schedule, student_group, date + ) + + for d in absent: + make_attendance_records( + d["student"], d["student_name"], "Absent", course_schedule, student_group, date + ) + + frappe.db.commit() + frappe.msgprint(_("Attendance has been marked successfully.")) + + +def make_attendance_records( + student, student_name, status, course_schedule=None, student_group=None, date=None +): + """Creates/Update Attendance Record. + + :param student: Student. + :param student_name: Student Name. + :param course_schedule: Course Schedule. + :param status: Status (Present/Absent) + """ + student_attendance = frappe.get_doc( + { + "doctype": "Student Attendance", + "student": student, + "course_schedule": course_schedule, + "student_group": student_group, + "date": date, + } + ) + if not student_attendance: + student_attendance = frappe.new_doc("Student Attendance") + student_attendance.student = student + student_attendance.student_name = student_name + student_attendance.course_schedule = course_schedule + student_attendance.student_group = student_group + student_attendance.date = date + student_attendance.status = status + student_attendance.save() + student_attendance.submit() + + +@frappe.whitelist() +def get_student_guardians(student): + """Returns List of Guardians of a Student. + + :param student: Student. + """ + guardians = frappe.get_all( + "Student Guardian", fields=["guardian"], filters={"parent": student} + ) + return guardians + + +@frappe.whitelist() +def get_student_group_students(student_group, include_inactive=0): + """Returns List of student, student_name in Student Group. + + :param student_group: Student Group. + """ + if include_inactive: + students = frappe.get_all( + "Student Group Student", + fields=["student", "student_name"], + filters={"parent": student_group}, + order_by="group_roll_number", + ) + else: + students = frappe.get_all( + "Student Group Student", + fields=["student", "student_name"], + filters={"parent": student_group, "active": 1}, + order_by="group_roll_number", + ) + return students + + +@frappe.whitelist() +def get_fee_structure(program, academic_term=None): + """Returns Fee Structure. + + :param program: Program. + :param academic_term: Academic Term. + """ + fee_structure = frappe.db.get_values( + "Fee Structure", + {"program": program, "academic_term": academic_term}, + "name", + as_dict=True, + ) + return fee_structure[0].name if fee_structure else None + + +@frappe.whitelist() +def get_fee_components(fee_structure): + """Returns Fee Components. + + :param fee_structure: Fee Structure. + """ + if fee_structure: + fs = frappe.get_all( + "Fee Component", + fields=["fees_category", "description", "amount"], + filters={"parent": fee_structure}, + order_by="idx", + ) + return fs + + +@frappe.whitelist() +def get_fee_schedule(program, student_category=None): + """Returns Fee Schedule. + + :param program: Program. + :param student_category: Student Category + """ + fs = frappe.get_all( + "Program Fee", + fields=["academic_term", "fee_structure", "due_date", "amount"], + filters={"parent": program, "student_category": student_category}, + order_by="idx", + ) + return fs + + +@frappe.whitelist() +def collect_fees(fees, amt): + paid_amount = flt(amt) + flt(frappe.db.get_value("Fees", fees, "paid_amount")) + total_amount = flt(frappe.db.get_value("Fees", fees, "total_amount")) + frappe.db.set_value("Fees", fees, "paid_amount", paid_amount) + frappe.db.set_value("Fees", fees, "outstanding_amount", (total_amount - paid_amount)) + return paid_amount + + +@frappe.whitelist() +def get_course_schedule_events(start, end, filters=None): + """Returns events for Course Schedule Calendar view rendering. + + :param start: Start date-time. + :param end: End date-time. + :param filters: Filters (JSON). + """ + from frappe.desk.calendar import get_event_conditions + + conditions = get_event_conditions("Course Schedule", filters) + + data = frappe.db.sql( + """select name, course, color, + timestamp(schedule_date, from_time) as from_time, + timestamp(schedule_date, to_time) as to_time, + room, student_group, 0 as 'allDay' + from `tabCourse Schedule` + where ( schedule_date between %(start)s and %(end)s ) + {conditions}""".format( + conditions=conditions + ), + {"start": start, "end": end}, + as_dict=True, + update={"allDay": 0}, + ) + + return data + + +@frappe.whitelist() +def get_assessment_criteria(course): + """Returns Assessmemt Criteria and their Weightage from Course Master. + + :param Course: Course + """ + return frappe.get_all( + "Course Assessment Criteria", + fields=["assessment_criteria", "weightage"], + filters={"parent": course}, + order_by="idx", + ) + + +@frappe.whitelist() +def get_assessment_students(assessment_plan, student_group): + student_list = get_student_group_students(student_group) + for i, student in enumerate(student_list): + result = get_result(student.student, assessment_plan) + if result: + student_result = {} + for d in result.details: + student_result.update({d.assessment_criteria: [cstr(d.score), d.grade]}) + student_result.update( + {"total_score": [cstr(result.total_score), result.grade], "comment": result.comment} + ) + student.update( + { + "assessment_details": student_result, + "docstatus": result.docstatus, + "name": result.name, + } + ) + else: + student.update({"assessment_details": None}) + return student_list + + +@frappe.whitelist() +def get_assessment_details(assessment_plan): + """Returns Assessment Criteria and Maximum Score from Assessment Plan Master. + + :param Assessment Plan: Assessment Plan + """ + return frappe.get_all( + "Assessment Plan Criteria", + fields=["assessment_criteria", "maximum_score", "docstatus"], + filters={"parent": assessment_plan}, + order_by="idx", + ) + + +@frappe.whitelist() +def get_result(student, assessment_plan): + """Returns Submitted Result of given student for specified Assessment Plan + + :param Student: Student + :param Assessment Plan: Assessment Plan + """ + results = frappe.get_all( + "Assessment Result", + filters={ + "student": student, + "assessment_plan": assessment_plan, + "docstatus": ("!=", 2), + }, + ) + if results: + return frappe.get_doc("Assessment Result", results[0]) + else: + return None + + +@frappe.whitelist() +def get_grade(grading_scale, percentage): + """Returns Grade based on the Grading Scale and Score. + + :param Grading Scale: Grading Scale + :param Percentage: Score Percentage Percentage + """ + grading_scale_intervals = {} + if not hasattr(frappe.local, "grading_scale"): + grading_scale = frappe.get_all( + "Grading Scale Interval", + fields=["grade_code", "threshold"], + filters={"parent": grading_scale}, + ) + frappe.local.grading_scale = grading_scale + for d in frappe.local.grading_scale: + grading_scale_intervals.update({d.threshold: d.grade_code}) + intervals = sorted(grading_scale_intervals.keys(), key=float, reverse=True) + for interval in intervals: + if flt(percentage) >= interval: + grade = grading_scale_intervals.get(interval) + break + else: + grade = "" + return grade + + +@frappe.whitelist() +def mark_assessment_result(assessment_plan, scores): + student_score = json.loads(scores) + assessment_details = [] + for criteria in student_score.get("assessment_details"): + assessment_details.append( + { + "assessment_criteria": criteria, + "score": flt(student_score["assessment_details"][criteria]), + } + ) + assessment_result = get_assessment_result_doc( + student_score["student"], assessment_plan + ) + assessment_result.update( + { + "student": student_score.get("student"), + "assessment_plan": assessment_plan, + "comment": student_score.get("comment"), + "total_score": student_score.get("total_score"), + "details": assessment_details, + } + ) + assessment_result.save() + details = {} + for d in assessment_result.details: + details.update({d.assessment_criteria: d.grade}) + assessment_result_dict = { + "name": assessment_result.name, + "student": assessment_result.student, + "total_score": assessment_result.total_score, + "grade": assessment_result.grade, + "details": details, + } + return assessment_result_dict + + +@frappe.whitelist() +def submit_assessment_results(assessment_plan, student_group): + total_result = 0 + student_list = get_student_group_students(student_group) + for i, student in enumerate(student_list): + doc = get_result(student.student, assessment_plan) + if doc and doc.docstatus == 0: + total_result += 1 + doc.submit() + return total_result + + +def get_assessment_result_doc(student, assessment_plan): + assessment_result = frappe.get_all( + "Assessment Result", + filters={ + "student": student, + "assessment_plan": assessment_plan, + "docstatus": ("!=", 2), + }, + ) + if assessment_result: + doc = frappe.get_doc("Assessment Result", assessment_result[0]) + if doc.docstatus == 0: + return doc + elif doc.docstatus == 1: + frappe.msgprint(_("Result already Submitted")) + return None + else: + return frappe.new_doc("Assessment Result") + + +@frappe.whitelist() +def update_email_group(doctype, name): + if not frappe.db.exists("Email Group", name): + email_group = frappe.new_doc("Email Group") + email_group.title = name + email_group.save() + email_list = [] + students = [] + if doctype == "Student Group": + students = get_student_group_students(name) + for stud in students: + for guard in get_student_guardians(stud.student): + email = frappe.db.get_value("Guardian", guard.guardian, "email_address") + if email: + email_list.append(email) + add_subscribers(name, email_list) + + +@frappe.whitelist() +def get_current_enrollment(student, academic_year=None): + current_academic_year = academic_year or frappe.defaults.get_defaults().academic_year + program_enrollment_list = frappe.db.sql( + """ + select + name as program_enrollment, student_name, program, student_batch_name as student_batch, + student_category, academic_term, academic_year + from + `tabProgram Enrollment` + where + student = %s and academic_year = %s + order by creation""", + (student, current_academic_year), + as_dict=1, + ) + + if program_enrollment_list: + return program_enrollment_list[0] + else: + return None diff --git a/education/education/doctype/__init__.py b/education/education/doctype/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/academic_term/__init__.py b/education/education/doctype/academic_term/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/academic_term/academic_term.js b/education/education/doctype/academic_term/academic_term.js new file mode 100644 index 0000000..e8b7956 --- /dev/null +++ b/education/education/doctype/academic_term/academic_term.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Academic Term', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/academic_term/academic_term.json b/education/education/doctype/academic_term/academic_term.json new file mode 100644 index 0000000..06c5b1a --- /dev/null +++ b/education/education/doctype/academic_term/academic_term.json @@ -0,0 +1,216 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:title", + "beta": 0, + "creation": "2015-09-08 17:19:19.158228", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year", + "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": 1, + "label": "Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "term_name", + "fieldtype": "Data", + "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": "Term Name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "term_start_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": "Term Start 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": 0, + "search_index": 1, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "term_end_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": "Term End 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": 0, + "search_index": 1, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Title", + "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, + "unique": 0 + } + ], + "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": "2017-11-10 19:05:58.567627", + "modified_by": "Administrator", + "module": "Education", + "name": "Academic Term", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "name", + "sort_order": "DESC", + "title_field": "title", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/academic_term/academic_term.py b/education/education/doctype/academic_term/academic_term.py new file mode 100644 index 0000000..edb3703 --- /dev/null +++ b/education/education/doctype/academic_term/academic_term.py @@ -0,0 +1,74 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import getdate + + +class AcademicTerm(Document): + def autoname(self): + self.name = ( + self.academic_year + " ({})".format(self.term_name) if self.term_name else "" + ) + + def validate(self): + # Check if entry with same academic_year and the term_name already exists + validate_duplication(self) + self.title = ( + self.academic_year + " ({})".format(self.term_name) if self.term_name else "" + ) + + # Check that start of academic year is earlier than end of academic year + if ( + self.term_start_date + and self.term_end_date + and getdate(self.term_start_date) > getdate(self.term_end_date) + ): + frappe.throw( + _( + "The Term End Date cannot be earlier than the Term Start Date. Please correct the dates and try again." + ) + ) + + # Check that the start of the term is not before the start of the academic year + # and end of term is not after the end of the academic year""" + + year = frappe.get_doc("Academic Year", self.academic_year) + if ( + self.term_start_date + and getdate(year.year_start_date) + and (getdate(self.term_start_date) < getdate(year.year_start_date)) + ): + frappe.throw( + _( + "The Term Start Date cannot be earlier than the Year Start Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again." + ).format(self.academic_year) + ) + + if ( + self.term_end_date + and getdate(year.year_end_date) + and (getdate(self.term_end_date) > getdate(year.year_end_date)) + ): + frappe.throw( + _( + "The Term End Date cannot be later than the Year End Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again." + ).format(self.academic_year) + ) + + +def validate_duplication(self): + term = frappe.db.sql( + """select name from `tabAcademic Term` where academic_year= %s and term_name= %s + and docstatus<2 and name != %s""", + (self.academic_year, self.term_name, self.name), + ) + if term: + frappe.throw( + _( + "An academic term with this 'Academic Year' {0} and 'Term Name' {1} already exists. Please modify these entries and try again." + ).format(self.academic_year, self.term_name) + ) diff --git a/education/education/doctype/academic_term/academic_term_dashboard.py b/education/education/doctype/academic_term/academic_term_dashboard.py new file mode 100644 index 0000000..69b34bc --- /dev/null +++ b/education/education/doctype/academic_term/academic_term_dashboard.py @@ -0,0 +1,16 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "academic_term", + "transactions": [ + { + "label": _("Student"), + "items": ["Student Applicant", "Student Group", "Student Log"], + }, + {"label": _("Fee"), "items": ["Fees", "Fee Schedule", "Fee Structure"]}, + {"label": _("Program"), "items": ["Program Enrollment"]}, + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + ], + } diff --git a/education/education/doctype/academic_term/test_academic_term.py b/education/education/doctype/academic_term/test_academic_term.py new file mode 100644 index 0000000..b737855 --- /dev/null +++ b/education/education/doctype/academic_term/test_academic_term.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Academic Term') + + +class TestAcademicTerm(unittest.TestCase): + pass diff --git a/education/education/doctype/academic_term/test_records.json b/education/education/doctype/academic_term/test_records.json new file mode 100644 index 0000000..6bd3655 --- /dev/null +++ b/education/education/doctype/academic_term/test_records.json @@ -0,0 +1,27 @@ +[ + { + "doctype": "Academic Term", + "academic_year": "2014-2015", + "term_name": "_Test Academic Term" + }, + { + "doctype": "Academic Term", + "academic_year": "2014-2015", + "term_name": "_Test Academic Term 1" + }, + { + "doctype": "Academic Term", + "academic_year": "2014-2015", + "term_name": "_Test Academic Term 2" + }, + { + "doctype": "Academic Term", + "academic_year": "2017-2018", + "term_name": "_Test AT1" + }, + { + "doctype": "Academic Term", + "academic_year": "2017-2018", + "term_name": "_Test AT2" + } +] \ No newline at end of file diff --git a/education/education/doctype/academic_year/__init__.py b/education/education/doctype/academic_year/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/academic_year/academic_year.js b/education/education/doctype/academic_year/academic_year.js new file mode 100644 index 0000000..20e2528 --- /dev/null +++ b/education/education/doctype/academic_year/academic_year.js @@ -0,0 +1,2 @@ +frappe.ui.form.on("Academic Year", { +}); diff --git a/education/education/doctype/academic_year/academic_year.json b/education/education/doctype/academic_year/academic_year.json new file mode 100644 index 0000000..5df89a7 --- /dev/null +++ b/education/education/doctype/academic_year/academic_year.json @@ -0,0 +1,154 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:academic_year_name", + "beta": 0, + "creation": "2015-09-07 12:49:51.303026", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year_name", + "fieldtype": "Data", + "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": "Academic Year Name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "year_start_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": "Year Start 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": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "year_end_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": "Year End 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": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "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": "2017-11-10 19:06:08.123090", + "modified_by": "Administrator", + "module": "Education", + "name": "Academic Year", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "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": "", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/academic_year/academic_year.py b/education/education/doctype/academic_year/academic_year.py new file mode 100644 index 0000000..b3a568d --- /dev/null +++ b/education/education/doctype/academic_year/academic_year.py @@ -0,0 +1,22 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class AcademicYear(Document): + def validate(self): + # Check that start of academic year is earlier than end of academic year + if ( + self.year_start_date + and self.year_end_date + and self.year_start_date > self.year_end_date + ): + frappe.throw( + _( + "The Year End Date cannot be earlier than the Year Start Date. Please correct the dates and try again." + ) + ) diff --git a/education/education/doctype/academic_year/academic_year_dashboard.py b/education/education/doctype/academic_year/academic_year_dashboard.py new file mode 100644 index 0000000..98a3b0e --- /dev/null +++ b/education/education/doctype/academic_year/academic_year_dashboard.py @@ -0,0 +1,19 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "academic_year", + "transactions": [ + { + "label": _("Student"), + "items": ["Student Admission", "Student Applicant", "Student Group", "Student Log"], + }, + {"label": _("Fee"), "items": ["Fees", "Fee Schedule", "Fee Structure"]}, + { + "label": _("Academic Term and Program"), + "items": ["Academic Term", "Program Enrollment"], + }, + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + ], + } diff --git a/education/education/doctype/academic_year/test_academic_year.py b/education/education/doctype/academic_year/test_academic_year.py new file mode 100644 index 0000000..dff13a9 --- /dev/null +++ b/education/education/doctype/academic_year/test_academic_year.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Academic Year') + + +class TestAcademicYear(unittest.TestCase): + pass diff --git a/education/education/doctype/academic_year/test_records.json b/education/education/doctype/academic_year/test_records.json new file mode 100644 index 0000000..5eb5e2e --- /dev/null +++ b/education/education/doctype/academic_year/test_records.json @@ -0,0 +1,18 @@ +[ + { + "doctype": "Academic Year", + "academic_year_name": "2014-2015" + }, + { + "doctype": "Academic Year", + "academic_year_name": "2015-2016" + }, + { + "doctype": "Academic Year", + "academic_year_name": "2016-2017" + }, + { + "doctype": "Academic Year", + "academic_year_name": "2017-2018" + } +] \ No newline at end of file diff --git a/education/education/doctype/article/__init__.py b/education/education/doctype/article/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/article/article.js b/education/education/doctype/article/article.js new file mode 100644 index 0000000..c7eaa19 --- /dev/null +++ b/education/education/doctype/article/article.js @@ -0,0 +1,56 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Article', { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__('Add to Topics'), function() { + frm.trigger('add_article_to_topics'); + }, __('Action')); + } + }, + + add_article_to_topics: function(frm) { + get_topics_without_article(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'topics', + label: __('Topics'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'education.education.doctype.topic.topic.add_content_to_topics', + args: { + 'content_type': 'Article', + 'content': frm.doc.name, + 'topics': data.topics, + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Article to Topics') + }); + }, __('Add Article to Topics'), __('Add')); + } else { + frappe.msgprint(__('This article is already added to the existing topics')); + } + }); + } +}); + +let get_topics_without_article = function(article) { + return frappe.call({ + type: 'GET', + method: 'education.education.doctype.article.article.get_topics_without_article', + args: {'article': article} + }); +}; diff --git a/education/education/doctype/article/article.json b/education/education/doctype/article/article.json new file mode 100644 index 0000000..2fad5af --- /dev/null +++ b/education/education/doctype/article/article.json @@ -0,0 +1,81 @@ +{ + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:title", + "creation": "2018-10-17 05:45:38.471670", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "author", + "content", + "publish_date" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "unique": 1 + }, + { + "fieldname": "author", + "fieldtype": "Data", + "label": "Author" + }, + { + "fieldname": "content", + "fieldtype": "Text Editor", + "label": "Content" + }, + { + "fieldname": "publish_date", + "fieldtype": "Date", + "label": "Publish Date" + } + ], + "modified": "2019-06-12 12:36:58.740340", + "modified_by": "Administrator", + "module": "Education", + "name": "Article", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/article/article.py b/education/education/doctype/article/article.py new file mode 100644 index 0000000..c7fd897 --- /dev/null +++ b/education/education/doctype/article/article.py @@ -0,0 +1,22 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class Article(Document): + def get_article(self): + pass + + +@frappe.whitelist() +def get_topics_without_article(article): + data = [] + for entry in frappe.db.get_all("Topic"): + topic = frappe.get_doc("Topic", entry.name) + topic_contents = [tc.content for tc in topic.topic_content] + if not topic_contents or article not in topic_contents: + data.append(topic.name) + return data diff --git a/education/education/doctype/article/test_article.py b/education/education/doctype/article/test_article.py new file mode 100644 index 0000000..3541074 --- /dev/null +++ b/education/education/doctype/article/test_article.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestArticle(unittest.TestCase): + pass diff --git a/education/education/doctype/assessment_criteria/__init__.py b/education/education/doctype/assessment_criteria/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_criteria/assessment_criteria.js b/education/education/doctype/assessment_criteria/assessment_criteria.js new file mode 100644 index 0000000..7ac8d23 --- /dev/null +++ b/education/education/doctype/assessment_criteria/assessment_criteria.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Assessment Criteria', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/assessment_criteria/assessment_criteria.json b/education/education/doctype/assessment_criteria/assessment_criteria.json new file mode 100644 index 0000000..9e228fd --- /dev/null +++ b/education/education/doctype/assessment_criteria/assessment_criteria.json @@ -0,0 +1,125 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:assessment_criteria", + "beta": 0, + "creation": "2016-12-14 16:40:15.144115", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_criteria", + "fieldtype": "Data", + "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": "Assessment Criteria", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_criteria_group", + "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": "Assessment Criteria Group", + "length": 0, + "no_copy": 0, + "options": "Assessment Criteria 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, + "unique": 0 + } + ], + "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": "2017-11-10 19:08:11.311304", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Criteria", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/assessment_criteria/assessment_criteria.py b/education/education/doctype/assessment_criteria/assessment_criteria.py new file mode 100644 index 0000000..bf8eacd --- /dev/null +++ b/education/education/doctype/assessment_criteria/assessment_criteria.py @@ -0,0 +1,22 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + +STD_CRITERIA = [ + "total", + "total score", + "total grade", + "maximum score", + "score", + "grade", +] + + +class AssessmentCriteria(Document): + def validate(self): + if self.assessment_criteria.lower() in STD_CRITERIA: + frappe.throw(_("Can't create standard criteria. Please rename the criteria")) diff --git a/education/education/doctype/assessment_criteria/test_assessment_criteria.py b/education/education/doctype/assessment_criteria/test_assessment_criteria.py new file mode 100644 index 0000000..16c3e42 --- /dev/null +++ b/education/education/doctype/assessment_criteria/test_assessment_criteria.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Assessment Criteria') + + +class TestAssessmentCriteria(unittest.TestCase): + pass diff --git a/education/education/doctype/assessment_criteria/test_records.json b/education/education/doctype/assessment_criteria/test_records.json new file mode 100644 index 0000000..7af63b3 --- /dev/null +++ b/education/education/doctype/assessment_criteria/test_records.json @@ -0,0 +1,8 @@ +[ + { + "assessment_criteria": "_Test Assessment Criteria" + }, + { + "assessment_criteria": "_Test Assessment Criteria 1" + } +] \ No newline at end of file diff --git a/education/education/doctype/assessment_criteria_group/__init__.py b/education/education/doctype/assessment_criteria_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_criteria_group/assessment_criteria_group.js b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.js new file mode 100644 index 0000000..9df0e01 --- /dev/null +++ b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Assessment Criteria Group', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/assessment_criteria_group/assessment_criteria_group.json b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.json new file mode 100644 index 0000000..5765b4b --- /dev/null +++ b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.json @@ -0,0 +1,94 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:assessment_criteria_group", + "beta": 0, + "creation": "2017-01-27 15:17:38.855910", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_criteria_group", + "fieldtype": "Data", + "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": "Assessment Criteria Group", + "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, + "unique": 0 + } + ], + "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": "2017-11-10 19:11:45.334917", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Criteria Group", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/assessment_criteria_group/assessment_criteria_group.py b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.py new file mode 100644 index 0000000..ce9eb1a --- /dev/null +++ b/education/education/doctype/assessment_criteria_group/assessment_criteria_group.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class AssessmentCriteriaGroup(Document): + pass diff --git a/education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py b/education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py new file mode 100644 index 0000000..1a45670 --- /dev/null +++ b/education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Assessment Criteria Group') + + +class TestAssessmentCriteriaGroup(unittest.TestCase): + pass diff --git a/education/education/doctype/assessment_group/__init__.py b/education/education/doctype/assessment_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_group/assessment_group.js b/education/education/doctype/assessment_group/assessment_group.js new file mode 100644 index 0000000..4a6f284 --- /dev/null +++ b/education/education/doctype/assessment_group/assessment_group.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Assessment Group', { + onload: function(frm) { + frm.list_route = "Tree/Assessment Group"; + } +}); diff --git a/education/education/doctype/assessment_group/assessment_group.json b/education/education/doctype/assessment_group/assessment_group.json new file mode 100644 index 0000000..a8ffaf4 --- /dev/null +++ b/education/education/doctype/assessment_group/assessment_group.json @@ -0,0 +1,90 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:assessment_group_name", + "creation": "2016-08-04 04:42:48.319388", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "assessment_group_name", + "is_group", + "section_break_2", + "parent_assessment_group", + "lft", + "rgt", + "old_parent" + ], + "fields": [ + { + "fieldname": "assessment_group_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Assessment Group Name", + "reqd": 1, + "unique": 1 + }, + { + "default": "0", + "fieldname": "is_group", + "fieldtype": "Check", + "label": "Is Group" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "hidden": 1 + }, + { + "fieldname": "parent_assessment_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parent Assessment Group", + "options": "Assessment Group", + "reqd": 1 + }, + { + "fieldname": "lft", + "fieldtype": "Int", + "label": "lft" + }, + { + "fieldname": "rgt", + "fieldtype": "Int", + "label": "rgt" + }, + { + "fieldname": "old_parent", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "old_parent", + "options": "Assessment Group" + } + ], + "is_tree": 1, + "links": [], + "modified": "2020-03-18 18:01:14.710416", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Group", + "nsm_parent_field": "parent_assessment_group", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/assessment_group/assessment_group.py b/education/education/doctype/assessment_group/assessment_group.py new file mode 100644 index 0000000..43ca887 --- /dev/null +++ b/education/education/doctype/assessment_group/assessment_group.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class AssessmentGroup(Document): + pass diff --git a/education/education/doctype/assessment_group/assessment_group_dashboard.py b/education/education/doctype/assessment_group/assessment_group_dashboard.py new file mode 100644 index 0000000..4edd564 --- /dev/null +++ b/education/education/doctype/assessment_group/assessment_group_dashboard.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "assessment_group", + "transactions": [ + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]} + ], + } diff --git a/education/education/doctype/assessment_group/assessment_group_tree.js b/education/education/doctype/assessment_group/assessment_group_tree.js new file mode 100644 index 0000000..e0dfaa3 --- /dev/null +++ b/education/education/doctype/assessment_group/assessment_group_tree.js @@ -0,0 +1,3 @@ +frappe.treeview_settings["Assessment Group"] = { + +} diff --git a/education/education/doctype/assessment_group/test_assessment_group.py b/education/education/doctype/assessment_group/test_assessment_group.py new file mode 100644 index 0000000..7f2b10f --- /dev/null +++ b/education/education/doctype/assessment_group/test_assessment_group.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Assessment Group') + + +class TestAssessmentGroup(unittest.TestCase): + pass diff --git a/education/education/doctype/assessment_plan/__init__.py b/education/education/doctype/assessment_plan/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_plan/assessment_plan.js b/education/education/doctype/assessment_plan/assessment_plan.js new file mode 100644 index 0000000..b476207 --- /dev/null +++ b/education/education/doctype/assessment_plan/assessment_plan.js @@ -0,0 +1,78 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + + +frappe.ui.form.on('Assessment Plan', { + onload: function(frm) { + frm.set_query('assessment_group', function(doc, cdt, cdn) { + return{ + filters: { + 'is_group': 0 + } + }; + }); + frm.set_query('grading_scale', function(){ + return { + filters: { + docstatus: 1 + } + }; + }); + }, + + refresh: function(frm) { + if (frm.doc.docstatus == 1) { + frm.add_custom_button(__('Assessment Result Tool'), function() { + frappe.route_options = { + assessment_plan: frm.doc.name, + student_group: frm.doc.student_group + } + frappe.set_route('Form', 'Assessment Result Tool'); + }, __('Tools')); + } + + frm.set_query('course', function() { + return { + query: 'education.education.doctype.program_enrollment.program_enrollment.get_program_courses', + filters: { + 'program': frm.doc.program + } + }; + }); + + frm.set_query('academic_term', function() { + return { + filters: { + 'academic_year': frm.doc.academic_year + } + }; + }); + }, + + course: function(frm) { + if (frm.doc.course && frm.doc.maximum_assessment_score) { + frappe.call({ + method: 'education.education.api.get_assessment_criteria', + args: { + course: frm.doc.course + }, + callback: function(r) { + if (r.message) { + frm.doc.assessment_criteria = []; + $.each(r.message, function(i, d) { + var row = frappe.model.add_child(frm.doc, 'Assessment Plan Criteria', 'assessment_criteria'); + row.assessment_criteria = d.assessment_criteria; + row.maximum_score = d.weightage / 100 * frm.doc.maximum_assessment_score; + }); + } + refresh_field('assessment_criteria'); + + } + }); + } + }, + + maximum_assessment_score: function(frm) { + frm.trigger('course'); + } +}); diff --git a/education/education/doctype/assessment_plan/assessment_plan.json b/education/education/doctype/assessment_plan/assessment_plan.json new file mode 100644 index 0000000..5066fdf --- /dev/null +++ b/education/education/doctype/assessment_plan/assessment_plan.json @@ -0,0 +1,227 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "EDU-ASP-.YYYY.-.#####", + "creation": "2015-11-12 16:34:34.658092", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "student_group", + "assessment_name", + "assessment_group", + "grading_scale", + "column_break_2", + "program", + "course", + "academic_year", + "academic_term", + "section_break_5", + "schedule_date", + "room", + "examiner", + "examiner_name", + "column_break_4", + "from_time", + "to_time", + "supervisor", + "supervisor_name", + "section_break_20", + "maximum_assessment_score", + "assessment_criteria", + "amended_from" + ], + "fields": [ + { + "fieldname": "student_group", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, + { + "fieldname": "assessment_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Assessment Name" + }, + { + "fieldname": "assessment_group", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Assessment Group", + "options": "Assessment Group", + "reqd": 1 + }, + { + "fetch_from": "course.default_grading_scale", + "fetch_if_empty": 1, + "fieldname": "grading_scale", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Grading Scale", + "options": "Grading Scale", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fetch_from": "student_group.course", + "fetch_if_empty": 1, + "fieldname": "course", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, + { + "fetch_from": "student_group.program", + "fieldname": "program", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Program", + "options": "Program" + }, + { + "fetch_from": "student_group.academic_year", + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year" + }, + { + "fetch_from": "student_group.academic_term", + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Schedule" + }, + { + "default": "Today", + "fieldname": "schedule_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Schedule Date", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "room", + "fieldtype": "Link", + "label": "Room", + "options": "Room" + }, + { + "fieldname": "examiner", + "fieldtype": "Link", + "label": "Examiner", + "options": "Instructor" + }, + { + "fetch_from": "examiner.instructor_name", + "fieldname": "examiner_name", + "fieldtype": "Data", + "label": "Examiner Name", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "label": "From Time", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "label": "To Time", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "supervisor", + "fieldtype": "Link", + "label": "Supervisor", + "options": "Instructor" + }, + { + "fetch_from": "supervisor.instructor_name", + "fieldname": "supervisor_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Supervisor Name", + "read_only": 1 + }, + { + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "label": "Evaluate" + }, + { + "fieldname": "maximum_assessment_score", + "fieldtype": "Float", + "label": "Maximum Assessment Score", + "reqd": 1 + }, + { + "fieldname": "assessment_criteria", + "fieldtype": "Table", + "label": "Assessment Criteria", + "options": "Assessment Plan Criteria", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Assessment Plan", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-10-23 15:55:35.076251", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Plan", + "owner": "Administrator", + "permissions": [ + { + "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 + } + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "assessment_name" +} \ No newline at end of file diff --git a/education/education/doctype/assessment_plan/assessment_plan.py b/education/education/doctype/assessment_plan/assessment_plan.py new file mode 100644 index 0000000..51c9e14 --- /dev/null +++ b/education/education/doctype/assessment_plan/assessment_plan.py @@ -0,0 +1,60 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class AssessmentPlan(Document): + def validate(self): + self.validate_overlap() + self.validate_max_score() + self.validate_assessment_criteria() + + def validate_overlap(self): + """Validates overlap for Student Group, Instructor, Room""" + + from education.education.utils import validate_overlap_for + + # Validate overlapping course schedules. + if self.student_group: + validate_overlap_for(self, "Course Schedule", "student_group") + + validate_overlap_for(self, "Course Schedule", "instructor") + validate_overlap_for(self, "Course Schedule", "room") + + # validate overlapping assessment schedules. + if self.student_group: + validate_overlap_for(self, "Assessment Plan", "student_group") + + validate_overlap_for(self, "Assessment Plan", "room") + validate_overlap_for(self, "Assessment Plan", "supervisor", self.supervisor) + + def validate_max_score(self): + max_score = 0 + for d in self.assessment_criteria: + max_score += d.maximum_score + if self.maximum_assessment_score != max_score: + frappe.throw( + _("Sum of Scores of Assessment Criteria needs to be {0}.").format( + self.maximum_assessment_score + ) + ) + + def validate_assessment_criteria(self): + assessment_criteria_list = frappe.db.sql_list( + """ select apc.assessment_criteria + from `tabAssessment Plan` ap , `tabAssessment Plan Criteria` apc + where ap.name = apc.parent and ap.course=%s and ap.student_group=%s and ap.assessment_group=%s + and ap.name != %s and ap.docstatus=1""", + (self.course, self.student_group, self.assessment_group, self.name), + ) + for d in self.assessment_criteria: + if d.assessment_criteria in assessment_criteria_list: + frappe.throw( + _("You have already assessed for the assessment criteria {}.").format( + frappe.bold(d.assessment_criteria) + ) + ) diff --git a/education/education/doctype/assessment_plan/assessment_plan_dashboard.py b/education/education/doctype/assessment_plan/assessment_plan_dashboard.py new file mode 100644 index 0000000..dac6f69 --- /dev/null +++ b/education/education/doctype/assessment_plan/assessment_plan_dashboard.py @@ -0,0 +1,12 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "assessment_plan", + "transactions": [{"label": _("Assessment"), "items": ["Assessment Result"]}], + "reports": [{"label": _("Report"), "items": ["Assessment Plan Status"]}], + } diff --git a/education/education/doctype/assessment_plan/test_assessment_plan.py b/education/education/doctype/assessment_plan/test_assessment_plan.py new file mode 100644 index 0000000..6a80f3f --- /dev/null +++ b/education/education/doctype/assessment_plan/test_assessment_plan.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Assessment Plan') + + +class TestAssessmentPlan(unittest.TestCase): + pass diff --git a/education/education/doctype/assessment_plan_criteria/__init__.py b/education/education/doctype/assessment_plan_criteria/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.json b/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.json new file mode 100644 index 0000000..d9ad0cb --- /dev/null +++ b/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.json @@ -0,0 +1,134 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2016-12-14 17:20:27.738226", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_criteria", + "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": "Assessment Criteria", + "length": 0, + "no_copy": 0, + "options": "Assessment Criteria", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maximum_score", + "fieldtype": "Float", + "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": "Maximum Score", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:10:50.560006", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Plan Criteria", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.py b/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.py new file mode 100644 index 0000000..c1196ef --- /dev/null +++ b/education/education/doctype/assessment_plan_criteria/assessment_plan_criteria.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class AssessmentPlanCriteria(Document): + pass diff --git a/education/education/doctype/assessment_result/__init__.py b/education/education/doctype/assessment_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_result/assessment_result.js b/education/education/doctype/assessment_result/assessment_result.js new file mode 100644 index 0000000..2cc3a63 --- /dev/null +++ b/education/education/doctype/assessment_result/assessment_result.js @@ -0,0 +1,125 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Assessment Result', { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.trigger('setup_chart'); + } + + frm.get_field('details').grid.cannot_add_rows = true; + + frm.set_query('course', function() { + return { + query: 'education.education.doctype.program_enrollment.program_enrollment.get_program_courses', + filters: { + 'program': frm.doc.program + } + }; + }); + + frm.set_query('academic_term', function() { + return { + filters: { + 'academic_year': frm.doc.academic_year + } + }; + }); + }, + + onload: function(frm) { + frm.set_query('assessment_plan', function() { + return { + filters: { + docstatus: 1 + } + }; + }); + }, + + assessment_plan: function(frm) { + if (frm.doc.assessment_plan) { + frappe.call({ + method: 'education.education.api.get_assessment_details', + args: { + assessment_plan: frm.doc.assessment_plan + }, + callback: function(r) { + if (r.message) { + frappe.model.clear_table(frm.doc, 'details'); + $.each(r.message, function(i, d) { + var row = frm.add_child('details'); + row.assessment_criteria = d.assessment_criteria; + row.maximum_score = d.maximum_score; + }); + frm.refresh_field('details'); + } + } + }); + } + }, + + setup_chart: function(frm) { + let labels = []; + let maximum_scores = []; + let scores = []; + $.each(frm.doc.details, function(_i, e) { + labels.push(e.assessment_criteria); + maximum_scores.push(e.maximum_score); + scores.push(e.score); + }); + + if (labels.length && maximum_scores.length && scores.length) { + frm.dashboard.chart_area.empty().removeClass('hidden'); + new frappe.Chart('.form-graph', { + title: 'Assessment Results', + data: { + labels: labels, + datasets: [ + { + name: 'Maximum Score', + chartType: 'bar', + values: maximum_scores, + }, + { + name: 'Score Obtained', + chartType: 'bar', + values: scores, + } + ] + }, + colors: ['#4CA746', '#98D85B'], + type: 'bar' + }); + } + } +}); + +frappe.ui.form.on('Assessment Result Detail', { + score: function(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + + if (!d.maximum_score || !frm.doc.grading_scale) { + d.score = ''; + frappe.throw(__('Please fill in all the details to generate Assessment Result.')); + } + + if (d.score > d.maximum_score) { + frappe.throw(__('Score cannot be greater than Maximum Score')); + } + else { + frappe.call({ + method: 'education.education.api.get_grade', + args: { + grading_scale: frm.doc.grading_scale, + percentage: ((d.score/d.maximum_score) * 100) + }, + callback: function(r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, 'grade', r.message); + } + } + }); + } + } +}); diff --git a/education/education/doctype/assessment_result/assessment_result.json b/education/education/doctype/assessment_result/assessment_result.json new file mode 100644 index 0000000..7a893aa --- /dev/null +++ b/education/education/doctype/assessment_result/assessment_result.json @@ -0,0 +1,203 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "EDU-RES-.YYYY.-.#####", + "creation": "2015-11-13 17:18:06.468332", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_plan", + "program", + "course", + "academic_year", + "academic_term", + "column_break_3", + "student", + "student_name", + "student_group", + "assessment_group", + "grading_scale", + "section_break_5", + "details", + "section_break_8", + "maximum_score", + "column_break_11", + "total_score", + "grade", + "section_break_13", + "comment", + "amended_from" + ], + "fields": [ + { + "fieldname": "assessment_plan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Plan", + "options": "Assessment Plan", + "reqd": 1 + }, + { + "fetch_from": "assessment_plan.program", + "fieldname": "program", + "fieldtype": "Link", + "label": "Program", + "options": "Program" + }, + { + "fetch_from": "assessment_plan.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "Course" + }, + { + "fetch_from": "assessment_plan.academic_year", + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year" + }, + { + "fetch_from": "assessment_plan.academic_term", + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "student", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Student", + "options": "Student", + "reqd": 1 + }, + { + "fetch_from": "student.title", + "fieldname": "student_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Student Name", + "read_only": 1 + }, + { + "fetch_from": "assessment_plan.student_group", + "fieldname": "student_group", + "fieldtype": "Link", + "label": "Student Group", + "options": "Student Group" + }, + { + "fetch_from": "assessment_plan.assessment_group", + "fieldname": "assessment_group", + "fieldtype": "Link", + "label": "Assessment Group", + "options": "Assessment Group" + }, + { + "fetch_from": "assessment_plan.grading_scale", + "fieldname": "grading_scale", + "fieldtype": "Link", + "label": "Grading Scale", + "options": "Grading Scale", + "read_only": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Result" + }, + { + "fieldname": "details", + "fieldtype": "Table", + "label": "Details", + "options": "Assessment Result Detail", + "reqd": 1 + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, + { + "fetch_from": "assessment_plan.maximum_assessment_score", + "fieldname": "maximum_score", + "fieldtype": "Float", + "label": "Maximum Score", + "read_only": 1 + }, + { + "fetch_from": "assessment_plan.maximum_assessment_score", + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Score", + "read_only": 1 + }, + { + "fieldname": "grade", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Grade", + "read_only": 1 + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Summary" + }, + { + "fieldname": "comment", + "fieldtype": "Small Text", + "label": "Comment" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Assessment Result", + "print_hide": 1, + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-08-03 11:47:54.119486", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Result", + "owner": "Administrator", + "permissions": [ + { + "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 + } + ], + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "student_name" +} \ No newline at end of file diff --git a/education/education/doctype/assessment_result/assessment_result.py b/education/education/doctype/assessment_result/assessment_result.py new file mode 100644 index 0000000..c05483a --- /dev/null +++ b/education/education/doctype/assessment_result/assessment_result.py @@ -0,0 +1,59 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt +from frappe.utils.csvutils import getlink + +import education.education +from education.education.api import get_assessment_details, get_grade + + +class AssessmentResult(Document): + def validate(self): + education.education.validate_student_belongs_to_group( + self.student, self.student_group + ) + self.validate_maximum_score() + self.validate_grade() + self.validate_duplicate() + + def validate_maximum_score(self): + assessment_details = get_assessment_details(self.assessment_plan) + max_scores = {} + for d in assessment_details: + max_scores.update({d.assessment_criteria: d.maximum_score}) + + for d in self.details: + d.maximum_score = max_scores.get(d.assessment_criteria) + if d.score > d.maximum_score: + frappe.throw(_("Score cannot be greater than Maximum Score")) + + def validate_grade(self): + self.total_score = 0.0 + for d in self.details: + d.grade = get_grade(self.grading_scale, (flt(d.score) / d.maximum_score) * 100) + self.total_score += d.score + self.grade = get_grade( + self.grading_scale, (self.total_score / self.maximum_score) * 100 + ) + + def validate_duplicate(self): + assessment_result = frappe.get_list( + "Assessment Result", + filters={ + "name": ("not in", [self.name]), + "student": self.student, + "assessment_plan": self.assessment_plan, + "docstatus": ("!=", 2), + }, + ) + if assessment_result: + frappe.throw( + _("Assessment Result record {0} already exists.").format( + getlink("Assessment Result", assessment_result[0].name) + ) + ) diff --git a/education/education/doctype/assessment_result/assessment_result_dashboard.py b/education/education/doctype/assessment_result/assessment_result_dashboard.py new file mode 100644 index 0000000..fb4ab48 --- /dev/null +++ b/education/education/doctype/assessment_result/assessment_result_dashboard.py @@ -0,0 +1,15 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "reports": [ + { + "label": _("Reports"), + "items": ["Final Assessment Grades", "Course wise Assessment Report"], + } + ] + } diff --git a/education/education/doctype/assessment_result/test_assessment_result.py b/education/education/doctype/assessment_result/test_assessment_result.py new file mode 100644 index 0000000..c6d2562 --- /dev/null +++ b/education/education/doctype/assessment_result/test_assessment_result.py @@ -0,0 +1,17 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +from education.education.api import get_grade + +# test_records = frappe.get_test_records('Assessment Result') + + +class TestAssessmentResult(unittest.TestCase): + def test_grade(self): + grade = get_grade("_Test Grading Scale", 80) + self.assertEqual("A", grade) + + grade = get_grade("_Test Grading Scale", 70) + self.assertEqual("B", grade) diff --git a/education/education/doctype/assessment_result_detail/__init__.py b/education/education/doctype/assessment_result_detail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_result_detail/assessment_result_detail.json b/education/education/doctype/assessment_result_detail/assessment_result_detail.json new file mode 100644 index 0000000..450f41c --- /dev/null +++ b/education/education/doctype/assessment_result_detail/assessment_result_detail.json @@ -0,0 +1,66 @@ +{ + "actions": [], + "creation": "2016-12-14 17:44:35.583123", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_criteria", + "maximum_score", + "column_break_2", + "score", + "grade" + ], + "fields": [ + { + "columns": 4, + "fieldname": "assessment_criteria", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Criteria", + "options": "Assessment Criteria", + "read_only": 1, + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "maximum_score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Maximum Score", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Score", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "grade", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Grade", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-31 13:27:17.699022", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Result Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/assessment_result_detail/assessment_result_detail.py b/education/education/doctype/assessment_result_detail/assessment_result_detail.py new file mode 100644 index 0000000..d74727c --- /dev/null +++ b/education/education/doctype/assessment_result_detail/assessment_result_detail.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class AssessmentResultDetail(Document): + pass diff --git a/education/education/doctype/assessment_result_tool/__init__.py b/education/education/doctype/assessment_result_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/assessment_result_tool/assessment_result_tool.js b/education/education/doctype/assessment_result_tool/assessment_result_tool.js new file mode 100644 index 0000000..2353b92 --- /dev/null +++ b/education/education/doctype/assessment_result_tool/assessment_result_tool.js @@ -0,0 +1,162 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + + +frappe.ui.form.on('Assessment Result Tool', { + setup: function(frm) { + frm.add_fetch("assessment_plan", "student_group", "student_group"); + }, + + refresh: function(frm) { + if (frappe.route_options) { + frm.set_value("student_group", frappe.route_options.student_group); + frm.set_value("assessment_plan", frappe.route_options.assessment_plan); + frappe.route_options = null; + } else { + frm.trigger("assessment_plan"); + } + frm.disable_save(); + frm.page.clear_indicator(); + }, + + assessment_plan: function(frm) { + frm.doc.show_submit = false; + if(frm.doc.assessment_plan) { + if (!frm.doc.student_group) + return + frappe.call({ + method: "education.education.api.get_assessment_students", + args: { + "assessment_plan": frm.doc.assessment_plan, + "student_group": frm.doc.student_group + }, + callback: function(r) { + if (r.message) { + frm.doc.students = r.message; + frm.events.render_table(frm); + for (let value of r.message) { + if (!value.docstatus) { + frm.doc.show_submit = true; + break; + } + } + frm.events.submit_result(frm); + } + } + }); + } + }, + + render_table: function(frm) { + $(frm.fields_dict.result_html.wrapper).empty(); + let assessment_plan = frm.doc.assessment_plan; + frappe.call({ + method: "education.education.api.get_assessment_details", + args: { + assessment_plan: assessment_plan + }, + callback: function(r) { + frm.events.get_marks(frm, r.message); + } + }); + }, + + get_marks: function(frm, criteria_list) { + let max_total_score = 0; + criteria_list.forEach(function(c) { + max_total_score += c.maximum_score + }); + var result_table = $(frappe.render_template('assessment_result_tool', { + frm: frm, + students: frm.doc.students, + criteria: criteria_list, + max_total_score: max_total_score + })); + result_table.appendTo(frm.fields_dict.result_html.wrapper); + + result_table.on('change', 'input', function(e) { + let $input = $(e.target); + let student = $input.data().student; + let max_score = $input.data().maxScore; + let value = $input.val(); + if(value < 0) { + $input.val(0); + } else if(value > max_score) { + $input.val(max_score); + } + let total_score = 0; + let student_scores = {}; + student_scores["assessment_details"] = {} + result_table.find(`input[data-student=${student}].student-result-data`) + .each(function(el, input) { + let $input = $(input); + let criteria = $input.data().criteria; + let value = parseFloat($input.val()); + if (!Number.isNaN(value)) { + student_scores["assessment_details"][criteria] = value; + } + total_score += value; + }); + if(!Number.isNaN(total_score)) { + result_table.find(`span[data-student=${student}].total-score`).html(total_score); + } + if (Object.keys(student_scores["assessment_details"]).length === criteria_list.length) { + student_scores["student"] = student; + student_scores["total_score"] = total_score; + result_table.find(`[data-student=${student}].result-comment`) + .each(function(el, input){ + student_scores["comment"] = $(input).val(); + }); + frappe.call({ + method: "education.education.api.mark_assessment_result", + args: { + "assessment_plan": frm.doc.assessment_plan, + "scores": student_scores + }, + callback: function(r) { + let assessment_result = r.message; + if (!frm.doc.show_submit) { + frm.doc.show_submit = true; + frm.events.submit_result; + } + for (var criteria of Object.keys(assessment_result.details)) { + result_table.find(`[data-criteria=${criteria}][data-student=${assessment_result + .student}].student-result-grade`).each(function(e1, input) { + $(input).html(assessment_result.details[criteria]); + }); + } + result_table.find(`span[data-student=${assessment_result.student}].total-score-grade`).html(assessment_result.grade); + let link_span = result_table.find(`span[data-student=${assessment_result.student}].total-result-link`); + $(link_span).css("display", "block"); + $(link_span).find("a").attr("href", "/app/assessment-result/"+assessment_result.name); + } + }); + } + }); + }, + + submit_result: function(frm) { + if (frm.doc.show_submit) { + frm.page.set_primary_action(__("Submit"), function() { + frappe.call({ + method: "education.education.api.submit_assessment_results", + args: { + "assessment_plan": frm.doc.assessment_plan, + "student_group": frm.doc.student_group + }, + callback: function(r) { + if (r.message) { + frappe.msgprint(__("{0} Result submittted", [r.message])); + } else { + frappe.msgprint(__("No Result to submit")); + } + frm.events.assessment_plan(frm); + } + }); + }); + } + else { + frm.page.clear_primary_action(); + } + } +}); diff --git a/education/education/doctype/assessment_result_tool/assessment_result_tool.json b/education/education/doctype/assessment_result_tool/assessment_result_tool.json new file mode 100644 index 0000000..ddef100 --- /dev/null +++ b/education/education/doctype/assessment_result_tool/assessment_result_tool.json @@ -0,0 +1,235 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-01-05 12:27:48.951036", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "assessment_plan", + "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": "Assessment Plan", + "length": 0, + "no_copy": 0, + "options": "Assessment Plan", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "unique": 0 + }, + { + "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": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "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": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "assessment_plan", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "result_html", + "fieldtype": "HTML", + "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": "Result HTML", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 1, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-12-27 09:36:37.155890", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Result Tool", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Academics User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Instructor", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/assessment_result_tool/assessment_result_tool.py b/education/education/doctype/assessment_result_tool/assessment_result_tool.py new file mode 100644 index 0000000..bc8896b --- /dev/null +++ b/education/education/doctype/assessment_result_tool/assessment_result_tool.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class AssessmentResultTool(Document): + pass diff --git a/education/education/doctype/assessment_result_tool/test_assessment_result_tool.py b/education/education/doctype/assessment_result_tool/test_assessment_result_tool.py new file mode 100644 index 0000000..230e2f5 --- /dev/null +++ b/education/education/doctype/assessment_result_tool/test_assessment_result_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestAssessmentResultTool(unittest.TestCase): + pass diff --git a/education/education/doctype/content_activity/__init__.py b/education/education/doctype/content_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/content_activity/content_activity.json b/education/education/doctype/content_activity/content_activity.json new file mode 100644 index 0000000..b4c95da --- /dev/null +++ b/education/education/doctype/content_activity/content_activity.json @@ -0,0 +1,141 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-16 03:55:53.283893", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content", + "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": "Content", + "length": 0, + "no_copy": 0, + "options": "Content", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "content.content_type", + "fieldname": "content_type", + "fieldtype": "Data", + "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": "Content Type", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "last_activity", + "fieldtype": "Datetime", + "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": "Last Activity ", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-10-16 03:55:58.202436", + "modified_by": "Administrator", + "module": "Education", + "name": "Content Activity", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/content_activity/content_activity.py b/education/education/doctype/content_activity/content_activity.py new file mode 100644 index 0000000..b4dd4e4 --- /dev/null +++ b/education/education/doctype/content_activity/content_activity.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ContentActivity(Document): + pass diff --git a/education/education/doctype/content_question/__init__.py b/education/education/doctype/content_question/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/content_question/content_question.js b/education/education/doctype/content_question/content_question.js new file mode 100644 index 0000000..68f1761 --- /dev/null +++ b/education/education/doctype/content_question/content_question.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Content Question', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/content_question/content_question.json b/education/education/doctype/content_question/content_question.json new file mode 100644 index 0000000..d390e8e --- /dev/null +++ b/education/education/doctype/content_question/content_question.json @@ -0,0 +1,76 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-15 14:35:40.728454", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "question_link", + "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": "Question Link", + "length": 0, + "no_copy": 0, + "options": "Question", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-10-15 14:41:31.729083", + "modified_by": "Administrator", + "module": "Education", + "name": "Content Question", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/content_question/content_question.py b/education/education/doctype/content_question/content_question.py new file mode 100644 index 0000000..f449640 --- /dev/null +++ b/education/education/doctype/content_question/content_question.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ContentQuestion(Document): + pass diff --git a/education/education/doctype/content_question/test_content_question.py b/education/education/doctype/content_question/test_content_question.py new file mode 100644 index 0000000..ff2609a --- /dev/null +++ b/education/education/doctype/content_question/test_content_question.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestContentQuestion(unittest.TestCase): + pass diff --git a/education/education/doctype/course/__init__.py b/education/education/doctype/course/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course/course.js b/education/education/doctype/course/course.js new file mode 100644 index 0000000..86a2bdb --- /dev/null +++ b/education/education/doctype/course/course.js @@ -0,0 +1,79 @@ +frappe.ui.form.on('Course', { + refresh: function(frm) { + if (!cur_frm.doc.__islocal) { + frm.add_custom_button(__('Add to Programs'), function() { + frm.trigger('add_course_to_programs') + }, __('Action')); + } + + frm.set_query('default_grading_scale', function(){ + return { + filters: { + docstatus: 1 + } + } + }); + }, + + add_course_to_programs: function(frm) { + get_programs_without_course(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'programs', + label: __('Programs'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + }, + { + fieldtype: 'Check', + label: __('Is Mandatory'), + fieldname: 'mandatory', + } + ], + function(data) { + frappe.call({ + method: 'education.education.doctype.course.course.add_course_to_programs', + args: { + 'course': frm.doc.name, + 'programs': data.programs, + 'mandatory': data.mandatory + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Course to Programs') + }) + }, __('Add Course to Programs'), __('Add')); + } else { + frappe.msgprint(__('This course is already added to the existing programs')); + } + }); + } +}); + +frappe.ui.form.on('Course Topic', { + topics_add: function(frm){ + frm.fields_dict['topics'].grid.get_field('topic').get_query = function(doc){ + var topics_list = []; + if(!doc.__islocal) topics_list.push(doc.name); + $.each(doc.topics, function(idx, val){ + if (val.topic) topics_list.push(val.topic); + }); + return { filters: [['Topic', 'name', 'not in', topics_list]] }; + }; + } +}); + +let get_programs_without_course = function(course) { + return frappe.call({ + type: 'GET', + method: 'education.education.doctype.course.course.get_programs_without_course', + args: {'course': course} + }); +} diff --git a/education/education/doctype/course/course.json b/education/education/doctype/course/course.json new file mode 100644 index 0000000..da10db1 --- /dev/null +++ b/education/education/doctype/course/course.json @@ -0,0 +1,137 @@ +{ + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:course_name", + "creation": "2015-09-07 12:39:55.181893", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "course_name", + "department", + "section_break_6", + "topics", + "description", + "hero_image", + "assessment", + "default_grading_scale", + "assessment_criteria" + ], + "fields": [ + { + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Portal Settings" + }, + { + "fieldname": "topics", + "fieldtype": "Table", + "label": "Topics", + "options": "Course Topic" + }, + { + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Hero Image" + }, + { + "fieldname": "assessment", + "fieldtype": "Section Break", + "label": "Assessment" + }, + { + "fieldname": "default_grading_scale", + "fieldtype": "Link", + "label": "Default Grading Scale", + "options": "Grading Scale" + }, + { + "fieldname": "assessment_criteria", + "fieldtype": "Table", + "label": "Assessment Criteria", + "options": "Course Assessment Criteria" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + } + ], + "image_field": "hero_image", + "modified": "2020-03-29 12:50:27.677589", + "modified_by": "Administrator", + "module": "Education", + "name": "Course", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Education Manager", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "search_fields": "course_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/course/course.py b/education/education/doctype/course/course.py new file mode 100644 index 0000000..3fc66d1 --- /dev/null +++ b/education/education/doctype/course/course.py @@ -0,0 +1,61 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class Course(Document): + def validate(self): + self.validate_assessment_criteria() + + def validate_assessment_criteria(self): + if self.assessment_criteria: + total_weightage = 0 + for criteria in self.assessment_criteria: + total_weightage += criteria.weightage or 0 + if total_weightage != 100: + frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%")) + + def get_topics(self): + topic_data = [] + for topic in self.topics: + topic_doc = frappe.get_doc("Topic", topic.topic) + if topic_doc.topic_content: + topic_data.append(topic_doc) + return topic_data + + +@frappe.whitelist() +def add_course_to_programs(course, programs, mandatory=False): + programs = json.loads(programs) + for entry in programs: + program = frappe.get_doc("Program", entry) + program.append( + "courses", {"course": course, "course_name": course, "mandatory": mandatory} + ) + program.flags.ignore_mandatory = True + program.save() + frappe.db.commit() + frappe.msgprint( + _("Course {0} has been added to all the selected programs successfully.").format( + frappe.bold(course) + ), + title=_("Programs updated"), + indicator="green", + ) + + +@frappe.whitelist() +def get_programs_without_course(course): + data = [] + for entry in frappe.db.get_all("Program"): + program = frappe.get_doc("Program", entry.name) + courses = [c.course for c in program.courses] + if not courses or course not in courses: + data.append(program.name) + return data diff --git a/education/education/doctype/course/course_dashboard.py b/education/education/doctype/course/course_dashboard.py new file mode 100644 index 0000000..70160da --- /dev/null +++ b/education/education/doctype/course/course_dashboard.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "course", + "transactions": [ + { + "label": _("Program and Course"), + "items": ["Program", "Course Enrollment", "Course Schedule"], + }, + {"label": _("Student"), "items": ["Student Group"]}, + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + ], + } diff --git a/education/education/doctype/course/test_course.py b/education/education/doctype/course/test_course.py new file mode 100644 index 0000000..48196b5 --- /dev/null +++ b/education/education/doctype/course/test_course.py @@ -0,0 +1,52 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe + +from education.education.doctype.topic.test_topic import ( + make_topic, make_topic_and_linked_content) + +# test_records = frappe.get_test_records('Course') + + +class TestCourse(unittest.TestCase): + def setUp(self): + make_topic_and_linked_content( + "_Test Topic 1", [{"type": "Article", "name": "_Test Article 1"}] + ) + make_topic_and_linked_content( + "_Test Topic 2", [{"type": "Article", "name": "_Test Article 2"}] + ) + make_course_and_linked_topic("_Test Course 1", ["_Test Topic 1", "_Test Topic 2"]) + + def test_get_topics(self): + course = frappe.get_doc("Course", "_Test Course 1") + topics = course.get_topics() + self.assertEqual(topics[0].name, "_Test Topic 1") + self.assertEqual(topics[1].name, "_Test Topic 2") + frappe.db.rollback() + + +def make_course(name): + try: + course = frappe.get_doc("Course", name) + except frappe.DoesNotExistError: + course = frappe.get_doc( + {"doctype": "Course", "course_name": name, "course_code": name} + ).insert() + return course.name + + +def make_course_and_linked_topic(course_name, topic_name_list): + try: + course = frappe.get_doc("Course", course_name) + except frappe.DoesNotExistError: + make_course(course_name) + course = frappe.get_doc("Course", course_name) + topic_list = [make_topic(topic_name) for topic_name in topic_name_list] + for topic in topic_list: + course.append("topics", {"topic": topic}) + course.save() + return course diff --git a/education/education/doctype/course/test_records.json b/education/education/doctype/course/test_records.json new file mode 100644 index 0000000..1e7467a --- /dev/null +++ b/education/education/doctype/course/test_records.json @@ -0,0 +1,14 @@ +[ + { + "course_name": "TC100", + "course_abbreviation": "TC" + }, + { + "course_name": "TC101", + "course_abbreviation": "TC1" + }, + { + "course_name": "TC102", + "course_abbreviation": "TC2" + } +] \ No newline at end of file diff --git a/education/education/doctype/course_activity/__init__.py b/education/education/doctype/course_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_activity/course_activity.js b/education/education/doctype/course_activity/course_activity.js new file mode 100644 index 0000000..661ae91 --- /dev/null +++ b/education/education/doctype/course_activity/course_activity.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Course Activity', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/course_activity/course_activity.json b/education/education/doctype/course_activity/course_activity.json new file mode 100644 index 0000000..3e23c90 --- /dev/null +++ b/education/education/doctype/course_activity/course_activity.json @@ -0,0 +1,301 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "format:EDU-CA-{YYYY}-{#####}", + "beta": 1, + "creation": "2018-10-01 17:35:54.391413", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enrollment", + "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": "Course Enrollment", + "length": 0, + "no_copy": 0, + "options": "Course Enrollment", + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "enrollment.course", + "fieldname": "course", + "fieldtype": "Data", + "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": "Course", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "enrollment.student", + "fieldname": "student", + "fieldtype": "Data", + "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": "Student", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content_type", + "fieldtype": "Select", + "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": "Content Type", + "length": 0, + "no_copy": 0, + "options": "\nArticle\nVideo", + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content", + "fieldtype": "Dynamic 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": "Content", + "length": 0, + "no_copy": 0, + "options": "content_type", + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activity_date", + "fieldtype": "Datetime", + "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": "Activity 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": 1, + "translatable": 0, + "unique": 0 + } + ], + "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-12-06 11:53:08.006123", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Activity", + "name_case": "", + "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, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 1, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} diff --git a/education/education/doctype/course_activity/course_activity.py b/education/education/doctype/course_activity/course_activity.py new file mode 100644 index 0000000..6a66bf0 --- /dev/null +++ b/education/education/doctype/course_activity/course_activity.py @@ -0,0 +1,18 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class CourseActivity(Document): + def validate(self): + self.check_if_enrolled() + + def check_if_enrolled(self): + if frappe.db.exists("Course Enrollment", self.enrollment): + return True + else: + frappe.throw(_("Course Enrollment {0} does not exists").format(self.enrollment)) diff --git a/education/education/doctype/course_activity/test_course_activity.py b/education/education/doctype/course_activity/test_course_activity.py new file mode 100644 index 0000000..e294965 --- /dev/null +++ b/education/education/doctype/course_activity/test_course_activity.py @@ -0,0 +1,30 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + +import frappe + + +class TestCourseActivity(unittest.TestCase): + pass + + +def make_course_activity(enrollment, content_type, content): + activity = frappe.get_all( + "Course Activity", + filters={"enrollment": enrollment, "content_type": content_type, "content": content}, + ) + try: + activity = frappe.get_doc("Course Activity", activity[0]["name"]) + except (IndexError, frappe.DoesNotExistError): + activity = frappe.get_doc( + { + "doctype": "Course Activity", + "enrollment": enrollment, + "content_type": content_type, + "content": content, + "activity_date": frappe.utils.datetime.datetime.now(), + } + ).insert() + return activity diff --git a/education/education/doctype/course_assessment_criteria/__init__.py b/education/education/doctype/course_assessment_criteria/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_assessment_criteria/course_assessment_criteria.json b/education/education/doctype/course_assessment_criteria/course_assessment_criteria.json new file mode 100644 index 0000000..2d8c0b3 --- /dev/null +++ b/education/education/doctype/course_assessment_criteria/course_assessment_criteria.json @@ -0,0 +1,134 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2016-12-14 16:46:46.786353", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_criteria", + "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": "Assessment Criteria", + "length": 0, + "no_copy": 0, + "options": "Assessment Criteria", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "weightage", + "fieldtype": "Percent", + "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": "Weightage", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:10:44.710837", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Assessment Criteria", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/course_assessment_criteria/course_assessment_criteria.py b/education/education/doctype/course_assessment_criteria/course_assessment_criteria.py new file mode 100644 index 0000000..453d02b --- /dev/null +++ b/education/education/doctype/course_assessment_criteria/course_assessment_criteria.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class CourseAssessmentCriteria(Document): + pass diff --git a/education/education/doctype/course_content/__init__.py b/education/education/doctype/course_content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_content/course_content.js b/education/education/doctype/course_content/course_content.js new file mode 100644 index 0000000..167df22 --- /dev/null +++ b/education/education/doctype/course_content/course_content.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Course Content', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/course_content/course_content.json b/education/education/doctype/course_content/course_content.json new file mode 100644 index 0000000..378e560 --- /dev/null +++ b/education/education/doctype/course_content/course_content.json @@ -0,0 +1,142 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-01 13:04:09.313771", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "", + "fieldname": "content_type", + "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": 0, + "label": "Content Type", + "length": 0, + "no_copy": 0, + "options": "\nArticle\nVideo\nQuiz", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "content", + "fieldtype": "Dynamic 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": "Content", + "length": 0, + "no_copy": 0, + "options": "content_type", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-10-17 07:36:04.029818", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Content", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/course_content/course_content.py b/education/education/doctype/course_content/course_content.py new file mode 100644 index 0000000..1cd4624 --- /dev/null +++ b/education/education/doctype/course_content/course_content.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class CourseContent(Document): + pass diff --git a/education/education/doctype/course_content/test_course_content.py b/education/education/doctype/course_content/test_course_content.py new file mode 100644 index 0000000..11ad151 --- /dev/null +++ b/education/education/doctype/course_content/test_course_content.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestCourseContent(unittest.TestCase): + pass diff --git a/education/education/doctype/course_enrollment/__init__.py b/education/education/doctype/course_enrollment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_enrollment/course_enrollment.js b/education/education/doctype/course_enrollment/course_enrollment.js new file mode 100644 index 0000000..3c49278 --- /dev/null +++ b/education/education/doctype/course_enrollment/course_enrollment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Course Enrollment', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/course_enrollment/course_enrollment.json b/education/education/doctype/course_enrollment/course_enrollment.json new file mode 100644 index 0000000..6286ec1 --- /dev/null +++ b/education/education/doctype/course_enrollment/course_enrollment.json @@ -0,0 +1,233 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "format:EDU-CE-{YYYY}-{#####}", + "beta": 1, + "creation": "2018-10-15 15:35:39.375161", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "program_enrollment", + "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": "Program Enrollment", + "length": 0, + "no_copy": 0, + "options": "Program Enrollment", + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "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": 0, + "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": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "course", + "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", + "length": 0, + "no_copy": 0, + "options": "Course", + "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": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enrollment_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": "Enrollment 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": 1, + "translatable": 0, + "unique": 0 + } + ], + "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-11-25 18:59:01.742377", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Enrollment", + "name_case": "", + "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, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/course_enrollment/course_enrollment.py b/education/education/doctype/course_enrollment/course_enrollment.py new file mode 100644 index 0000000..8145494 --- /dev/null +++ b/education/education/doctype/course_enrollment/course_enrollment.py @@ -0,0 +1,114 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from functools import reduce + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import get_link_to_form + + +class CourseEnrollment(Document): + def validate(self): + self.validate_duplication() + + def get_progress(self, student): + """ + Returns Progress of given student for a particular course enrollment + + :param self: Course Enrollment Object + :param student: Student Object + """ + course = frappe.get_doc("Course", self.course) + topics = course.get_topics() + progress = [] + for topic in topics: + progress.append(student.get_topic_progress(self.name, topic)) + if progress: + return reduce(lambda x, y: x + y, progress) # Flatten out the List + else: + return [] + + def validate_duplication(self): + enrollment = frappe.db.exists( + "Course Enrollment", + { + "student": self.student, + "course": self.course, + "program_enrollment": self.program_enrollment, + "name": ("!=", self.name), + }, + ) + if enrollment: + frappe.throw( + _("Student is already enrolled via Course Enrollment {0}").format( + get_link_to_form("Course Enrollment", enrollment) + ), + title=_("Duplicate Entry"), + ) + + def add_quiz_activity( + self, quiz_name, quiz_response, answers, score, status, time_taken + ): + result = {k: ("Correct" if v else "Wrong") for k, v in answers.items()} + result_data = [] + for key in answers: + item = {} + item["question"] = key + item["quiz_result"] = result[key] + try: + if not quiz_response[key]: + item["selected_option"] = "Unattempted" + elif isinstance(quiz_response[key], list): + item["selected_option"] = ", ".join( + frappe.get_value("Options", res, "option") for res in quiz_response[key] + ) + else: + item["selected_option"] = frappe.get_value("Options", quiz_response[key], "option") + except KeyError: + item["selected_option"] = "Unattempted" + result_data.append(item) + + quiz_activity = frappe.get_doc( + { + "doctype": "Quiz Activity", + "enrollment": self.name, + "quiz": quiz_name, + "activity_date": frappe.utils.datetime.datetime.now(), + "result": result_data, + "score": score, + "status": status, + "time_taken": time_taken, + } + ).insert(ignore_permissions=True) + + def add_activity(self, content_type, content): + activity = check_activity_exists(self.name, content_type, content) + if activity: + return activity + else: + activity = frappe.get_doc( + { + "doctype": "Course Activity", + "enrollment": self.name, + "content_type": content_type, + "content": content, + "activity_date": frappe.utils.datetime.datetime.now(), + } + ) + + activity.insert(ignore_permissions=True) + return activity.name + + +def check_activity_exists(enrollment, content_type, content): + activity = frappe.get_all( + "Course Activity", + filters={"enrollment": enrollment, "content_type": content_type, "content": content}, + ) + if activity: + return activity[0].name + else: + return None diff --git a/education/education/doctype/course_enrollment/course_enrollment_dashboard.py b/education/education/doctype/course_enrollment/course_enrollment_dashboard.py new file mode 100644 index 0000000..acb1d8f --- /dev/null +++ b/education/education/doctype/course_enrollment/course_enrollment_dashboard.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "enrollment", + "transactions": [ + {"label": _("Activity"), "items": ["Course Activity", "Quiz Activity"]} + ], + } diff --git a/education/education/doctype/course_enrollment/test_course_enrollment.py b/education/education/doctype/course_enrollment/test_course_enrollment.py new file mode 100644 index 0000000..8a573ac --- /dev/null +++ b/education/education/doctype/course_enrollment/test_course_enrollment.py @@ -0,0 +1,67 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + +import frappe + +from education.education.doctype.course_activity.test_course_activity import \ + make_course_activity +from education.education.doctype.program.test_program import setup_program +from education.education.doctype.student.test_student import (create_student, + get_student) + + +class TestCourseEnrollment(unittest.TestCase): + def setUp(self): + setup_program() + student = create_student( + { + "first_name": "_Test First", + "last_name": "_Test Last", + "email": "_test_student_1@example.com", + } + ) + program_enrollment = student.enroll_in_program("_Test Program") + course_enrollment = frappe.db.get_value( + "Course Enrollment", + { + "course": "_Test Course 1", + "student": student.name, + "program_enrollment": program_enrollment.name, + }, + "name", + ) + make_course_activity(course_enrollment, "Article", "_Test Article 1-1") + + def test_get_progress(self): + student = get_student("_test_student_1@example.com") + program_enrollment_name = frappe.get_list( + "Program Enrollment", filters={"student": student.name, "Program": "_Test Program"} + )[0].name + course_enrollment_name = frappe.get_list( + "Course Enrollment", + filters={ + "student": student.name, + "course": "_Test Course 1", + "program_enrollment": program_enrollment_name, + }, + )[0].name + course_enrollment = frappe.get_doc("Course Enrollment", course_enrollment_name) + progress = course_enrollment.get_progress(student) + finished = { + "content": "_Test Article 1-1", + "content_type": "Article", + "is_complete": True, + } + self.assertTrue(finished in progress) + frappe.db.rollback() + + def tearDown(self): + for entry in frappe.db.get_all("Course Enrollment"): + frappe.delete_doc("Course Enrollment", entry.name) + + for entry in frappe.db.get_all("Program Enrollment"): + doc = frappe.get_doc("Program Enrollment", entry.name) + doc.cancel() + doc.delete() diff --git a/education/education/doctype/course_schedule/__init__.py b/education/education/doctype/course_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_schedule/course_schedule.js b/education/education/doctype/course_schedule/course_schedule.js new file mode 100644 index 0000000..366bbd8 --- /dev/null +++ b/education/education/doctype/course_schedule/course_schedule.js @@ -0,0 +1,16 @@ +frappe.provide("education"); + +cur_frm.add_fetch("student_group", "course", "course") +frappe.ui.form.on("Course Schedule", { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Mark Attendance"), function() { + frappe.route_options = { + based_on: "Course Schedule", + course_schedule: frm.doc.name + } + frappe.set_route("Form", "Student Attendance Tool"); + }).addClass("btn-primary"); + } + } +}); diff --git a/education/education/doctype/course_schedule/course_schedule.json b/education/education/doctype/course_schedule/course_schedule.json new file mode 100644 index 0000000..38d9b50 --- /dev/null +++ b/education/education/doctype/course_schedule/course_schedule.json @@ -0,0 +1,143 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2015-09-09 16:34:04.960369", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "student_group", + "instructor", + "instructor_name", + "column_break_2", + "naming_series", + "course", + "color", + "section_break_6", + "schedule_date", + "room", + "column_break_9", + "from_time", + "to_time", + "title" + ], + "fields": [ + { + "fieldname": "student_group", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, + { + "fieldname": "instructor", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Instructor", + "options": "Instructor", + "reqd": 1 + }, + { + "fetch_from": "instructor.instructor_name", + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "in_global_search": 1, + "label": "Instructor Name", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "EDU-CSH-.YYYY.-", + "set_only_once": 1 + }, + { + "fieldname": "course", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, + { + "fieldname": "color", + "fieldtype": "Color", + "label": "Color", + "print_hide": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "default": "Today", + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Schedule Date" + }, + { + "fieldname": "room", + "fieldtype": "Link", + "label": "Room", + "options": "Room", + "reqd": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title" + } + ], + "links": [], + "modified": "2021-11-24 11:57:08.164449", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Schedule", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "sort_field": "schedule_date", + "sort_order": "DESC", + "title_field": "title" +} \ No newline at end of file diff --git a/education/education/doctype/course_schedule/course_schedule.py b/education/education/doctype/course_schedule/course_schedule.py new file mode 100644 index 0000000..0773307 --- /dev/null +++ b/education/education/doctype/course_schedule/course_schedule.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from datetime import datetime + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class CourseSchedule(Document): + def validate(self): + self.instructor_name = frappe.db.get_value( + "Instructor", self.instructor, "instructor_name" + ) + self.set_title() + self.validate_course() + self.validate_date() + self.validate_overlap() + + def set_title(self): + """Set document Title""" + self.title = ( + self.course + + " by " + + (self.instructor_name if self.instructor_name else self.instructor) + ) + + def validate_course(self): + group_based_on, course = frappe.db.get_value( + "Student Group", self.student_group, ["group_based_on", "course"] + ) + if group_based_on == "Course": + self.course = course + + def validate_date(self): + """Validates if from_time is greater than to_time""" + if self.from_time > self.to_time: + frappe.throw(_("From Time cannot be greater than To Time.")) + + """Handles specicfic case to update schedule date in calendar """ + if isinstance(self.from_time, str): + try: + datetime_obj = datetime.strptime(self.from_time, "%Y-%m-%d %H:%M:%S") + self.schedule_date = datetime_obj + except ValueError: + pass + + def validate_overlap(self): + """Validates overlap for Student Group, Instructor, Room""" + + from education.education.utils import validate_overlap_for + + # Validate overlapping course schedules. + if self.student_group: + validate_overlap_for(self, "Course Schedule", "student_group") + + validate_overlap_for(self, "Course Schedule", "instructor") + validate_overlap_for(self, "Course Schedule", "room") + + # validate overlapping assessment schedules. + if self.student_group: + validate_overlap_for(self, "Assessment Plan", "student_group") + + validate_overlap_for(self, "Assessment Plan", "room") + validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor) diff --git a/education/education/doctype/course_schedule/course_schedule_calendar.js b/education/education/doctype/course_schedule/course_schedule_calendar.js new file mode 100644 index 0000000..fc8a69a --- /dev/null +++ b/education/education/doctype/course_schedule/course_schedule_calendar.js @@ -0,0 +1,38 @@ +frappe.views.calendar["Course Schedule"] = { + field_map: { + "start": "from_time", + "end": "to_time", + "id": "name", + "title": "course", + "allDay": "allDay", + }, + gantt: false, + order_by: "schedule_date", + filters: [ + { + "fieldtype": "Link", + "fieldname": "student_group", + "options": "Student Group", + "label": __("Student Group") + }, + { + "fieldtype": "Link", + "fieldname": "course", + "options": "Course", + "label": __("Course") + }, + { + "fieldtype": "Link", + "fieldname": "instructor", + "options": "Instructor", + "label": __("Instructor") + }, + { + "fieldtype": "Link", + "fieldname": "room", + "options": "Room", + "label": __("Room") + } + ], + get_events_method: "education.education.api.get_course_schedule_events" +} diff --git a/education/education/doctype/course_schedule/course_schedule_dashboard.py b/education/education/doctype/course_schedule/course_schedule_dashboard.py new file mode 100644 index 0000000..8786039 --- /dev/null +++ b/education/education/doctype/course_schedule/course_schedule_dashboard.py @@ -0,0 +1,11 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "course_schedule", + "transactions": [{"label": _("Attendance"), "items": ["Student Attendance"]}], + } diff --git a/education/education/doctype/course_schedule/test_course_schedule.py b/education/education/doctype/course_schedule/test_course_schedule.py new file mode 100644 index 0000000..d932cba --- /dev/null +++ b/education/education/doctype/course_schedule/test_course_schedule.py @@ -0,0 +1,102 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import datetime +import unittest + +import frappe +from frappe.utils import to_timedelta, today +from frappe.utils.data import add_to_date + +from education.education.utils import OverlapError + +# test_records = frappe.get_test_records('Course Schedule') + + +class TestCourseSchedule(unittest.TestCase): + def test_student_group_conflict(self): + cs1 = make_course_schedule_test_record(simulate=True) + + cs2 = make_course_schedule_test_record( + schedule_date=cs1.schedule_date, + from_time=cs1.from_time, + to_time=cs1.to_time, + instructor="_Test Instructor 2", + room=frappe.get_all("Room")[1].name, + do_not_save=1, + ) + self.assertRaises(OverlapError, cs2.save) + + def test_instructor_conflict(self): + cs1 = make_course_schedule_test_record(simulate=True) + + cs2 = make_course_schedule_test_record( + from_time=cs1.from_time, + to_time=cs1.to_time, + student_group="Course-TC101-2014-2015 (_Test Academic Term)", + room=frappe.get_all("Room")[1].name, + do_not_save=1, + ) + self.assertRaises(OverlapError, cs2.save) + + def test_room_conflict(self): + cs1 = make_course_schedule_test_record(simulate=True) + + cs2 = make_course_schedule_test_record( + from_time=cs1.from_time, + to_time=cs1.to_time, + student_group="Course-TC101-2014-2015 (_Test Academic Term)", + instructor="_Test Instructor 2", + do_not_save=1, + ) + self.assertRaises(OverlapError, cs2.save) + + def test_no_conflict(self): + cs1 = make_course_schedule_test_record(simulate=True) + + make_course_schedule_test_record( + from_time=cs1.from_time, + to_time=cs1.to_time, + student_group="Course-TC102-2014-2015 (_Test Academic Term)", + instructor="_Test Instructor 2", + room=frappe.get_all("Room")[1].name, + ) + + def test_update_schedule_date(self): + doc = make_course_schedule_test_record(schedule_date=add_to_date(today(), days=1)) + doc.schedule_date = add_to_date(doc.schedule_date, days=1) + doc.save() + + +def make_course_schedule_test_record(**args): + args = frappe._dict(args) + + course_schedule = frappe.new_doc("Course Schedule") + course_schedule.student_group = ( + args.student_group or "Course-TC101-2014-2015 (_Test Academic Term)" + ) + course_schedule.course = args.course or "TC101" + course_schedule.instructor = args.instructor or "_Test Instructor" + course_schedule.room = args.room or frappe.get_all("Room")[0].name + + course_schedule.schedule_date = args.schedule_date or today() + course_schedule.from_time = args.from_time or to_timedelta("01:00:00") + course_schedule.to_time = ( + args.to_time or course_schedule.from_time + datetime.timedelta(hours=1) + ) + + if not args.do_not_save: + if args.simulate: + while True: + try: + course_schedule.save() + break + except OverlapError: + course_schedule.from_time = course_schedule.from_time + datetime.timedelta( + minutes=10 + ) + course_schedule.to_time = course_schedule.from_time + datetime.timedelta(hours=1) + else: + course_schedule.save() + + return course_schedule diff --git a/education/education/doctype/course_scheduling_tool/__init__.py b/education/education/doctype/course_scheduling_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_scheduling_tool/course_scheduling_tool.js b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.js new file mode 100644 index 0000000..bfce17b --- /dev/null +++ b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.js @@ -0,0 +1,102 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + + +frappe.ui.form.on('Course Scheduling Tool', { + setup(frm) { + frm.add_fetch('student_group', 'program', 'program'); + frm.add_fetch('student_group', 'course', 'course'); + frm.add_fetch('student_group', 'academic_year', 'academic_year'); + frm.add_fetch('student_group', 'academic_term', 'academic_term'); + }, + refresh(frm) { + frm.disable_save(); + frm.trigger("render_days"); + frm.page.set_primary_action(__('Schedule Course'), () => { + frappe.dom.freeze(__("Scheduling...")); + frm.call('schedule_course', { days: frm.days.get_checked_options() }) + .then(r => { + frappe.dom.unfreeze(); + if (!r.message) { + frappe.throw(__('There were errors creating Course Schedule')); + } + const { course_schedules } = r.message; + if (course_schedules) { + const course_schedules_html = course_schedules.map(c => ` + + ${c.name} + ${c.schedule_date} + + `).join(''); + + const html = ` + + + + + ${course_schedules_html} + +
${__('Following course schedules were created')}
${__("Course")}${__("Date")}
+ `; + + frappe.msgprint(html); + } + }); + }); + }, + render_days: function(frm) { + const days_html = $('
').appendTo( + frm.fields_dict.days_html.wrapper + ); + + if (!frm.days) { + frm.days = frappe.ui.form.make_control({ + parent: days_html, + df: { + fieldname: "days", + fieldtype: "MultiCheck", + select_all: true, + columns: 4, + options: [ + { + label: __("Monday"), + value: "Monday", + checked: 0, + }, + { + label: __("Tuesday"), + value: "Tuesday", + checked: 0, + }, + { + label: __("Wednesday"), + value: "Wednesday", + checked: 0, + }, + { + label: __("Thursday"), + value: "Thursday", + checked: 0, + }, + { + label: __("Friday"), + value: "Friday", + checked: 0, + }, + { + label: __("Saturday"), + value: "Saturday", + checked: 0, + }, + { + label: __("Sunday"), + value: "Sunday", + checked: 0, + }, + ], + }, + render_input: true, + }); + } + } +}); diff --git a/education/education/doctype/course_scheduling_tool/course_scheduling_tool.json b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.json new file mode 100644 index 0000000..584a6d3 --- /dev/null +++ b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.json @@ -0,0 +1,171 @@ +{ + "actions": [], + "allow_copy": 1, + "creation": "2015-09-23 15:37:38.108475", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "student_group", + "course", + "program", + "column_break_3", + "academic_year", + "academic_term", + "section_break_6", + "instructor", + "instructor_name", + "column_break_9", + "room", + "section_break_7", + "days_html", + "section_break_14", + "from_time", + "course_start_date", + "column_break_15", + "to_time", + "course_end_date", + "reschedule" + ], + "fields": [ + { + "fieldname": "student_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, + { + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, + { + "fieldname": "program", + "fieldtype": "Link", + "label": "Program", + "options": "Program", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year", + "read_only": 1 + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term", + "read_only": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "instructor", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instructor", + "options": "Instructor", + "reqd": 1 + }, + { + "fetch_from": "instructor.instructor_name", + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "label": "Instructor Name", + "read_only": 1 + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "room", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Room", + "options": "Room", + "reqd": 1 + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "label": "From Time", + "reqd": 1 + }, + { + "fieldname": "course_start_date", + "fieldtype": "Date", + "label": "Course Start Date", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "reschedule", + "fieldtype": "Check", + "label": "Reschedule" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "label": "To TIme", + "reqd": 1 + }, + { + "fieldname": "course_end_date", + "fieldtype": "Date", + "label": "Course End Date", + "reqd": 1 + }, + { + "fieldname": "days_html", + "fieldtype": "HTML", + "label": "Days HTML" + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break" + } + ], + "hide_toolbar": 1, + "issingle": 1, + "links": [], + "modified": "2022-10-01 17:08:07.180557", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Scheduling Tool", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "read": 1, + "role": "Academics User", + "write": 1 + } + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 + } \ No newline at end of file diff --git a/education/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.py new file mode 100644 index 0000000..2c5ec09 --- /dev/null +++ b/education/education/doctype/course_scheduling_tool/course_scheduling_tool.py @@ -0,0 +1,119 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import calendar + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_days, getdate + +from education.education.utils import OverlapError + + +class CourseSchedulingTool(Document): + @frappe.whitelist() + def schedule_course(self, days): + """Creates course schedules as per specified parameters""" + + course_schedules = [] + course_schedules_errors = [] + rescheduled = [] + reschedule_errors = [] + + self.validate_mandatory(days) + self.validate_date() + self.instructor_name = frappe.db.get_value( + "Instructor", self.instructor, "instructor_name" + ) + + group_based_on, course = frappe.db.get_value( + "Student Group", self.student_group, ["group_based_on", "course"] + ) + + if group_based_on == "Course": + self.course = course + + if self.reschedule: + rescheduled, reschedule_errors = self.delete_course_schedule( + rescheduled, reschedule_errors, days + ) + + date = self.course_start_date + while date < self.course_end_date: + if calendar.day_name[getdate(date).weekday()] in days: + course_schedule = self.make_course_schedule(date) + try: + course_schedule.save() + except OverlapError: + course_schedules_errors.append(date) + else: + course_schedules.append(course_schedule) + + date = add_days(date, 1) + + return dict( + course_schedules=course_schedules, + course_schedules_errors=course_schedules_errors, + rescheduled=rescheduled, + reschedule_errors=reschedule_errors, + ) + + def validate_mandatory(self, days): + """Validates all mandatory fields""" + if not days: + frappe.throw(_("Please select at least one day to schedule the course.")) + fields = [ + "course", + "room", + "instructor", + "from_time", + "to_time", + "course_start_date", + "course_end_date", + ] + for d in fields: + if not self.get(d): + frappe.throw(_("{0} is mandatory").format(self.meta.get_label(d))) + + def validate_date(self): + """Validates if Course Start Date is greater than Course End Date""" + if self.course_start_date > self.course_end_date: + frappe.throw(_("Course Start Date cannot be greater than Course End Date.")) + + def delete_course_schedule(self, rescheduled, reschedule_errors, days): + """Delete all course schedule within the Date range and specified filters""" + schedules = frappe.get_list( + "Course Schedule", + fields=["name", "schedule_date"], + filters=[ + ["student_group", "=", self.student_group], + ["course", "=", self.course], + ["schedule_date", ">=", self.course_start_date], + ["schedule_date", "<=", self.course_end_date], + ], + ) + + for d in schedules: + try: + if calendar.day_name[getdate(d.schedule_date).weekday()] in days: + frappe.delete_doc("Course Schedule", d.name) + rescheduled.append(d.name) + except Exception: + reschedule_errors.append(d.name) + return rescheduled, reschedule_errors + + def make_course_schedule(self, date): + """Makes a new Course Schedule. + :param date: Date on which Course Schedule will be created.""" + course_schedule = frappe.new_doc("Course Schedule") + course_schedule.student_group = self.student_group + course_schedule.course = self.course + course_schedule.instructor = self.instructor + course_schedule.instructor_name = self.instructor_name + course_schedule.room = self.room + course_schedule.schedule_date = date + course_schedule.from_time = self.from_time + course_schedule.to_time = self.to_time + return course_schedule diff --git a/education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py b/education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py new file mode 100644 index 0000000..ec19b89 --- /dev/null +++ b/education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestCourseSchedulingTool(unittest.TestCase): + pass diff --git a/education/education/doctype/course_topic/__init__.py b/education/education/doctype/course_topic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/course_topic/course_topic.js b/education/education/doctype/course_topic/course_topic.js new file mode 100644 index 0000000..6e79c18 --- /dev/null +++ b/education/education/doctype/course_topic/course_topic.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Course Topic', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/course_topic/course_topic.json b/education/education/doctype/course_topic/course_topic.json new file mode 100644 index 0000000..3fcddc1 --- /dev/null +++ b/education/education/doctype/course_topic/course_topic.json @@ -0,0 +1,109 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-12-12 11:51:25.952740", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "topic", + "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": "Topic", + "length": 0, + "no_copy": 0, + "options": "Topic", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "topic.topic_name", + "fieldname": "topic_name", + "fieldtype": "Data", + "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": "Topic Name", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-12-12 13:01:58.960425", + "modified_by": "Administrator", + "module": "Education", + "name": "Course Topic", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/course_topic/course_topic.py b/education/education/doctype/course_topic/course_topic.py new file mode 100644 index 0000000..cb590fe --- /dev/null +++ b/education/education/doctype/course_topic/course_topic.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class CourseTopic(Document): + pass diff --git a/education/education/doctype/course_topic/test_course_topic.py b/education/education/doctype/course_topic/test_course_topic.py new file mode 100644 index 0000000..09afa4d --- /dev/null +++ b/education/education/doctype/course_topic/test_course_topic.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestCourseTopic(unittest.TestCase): + pass diff --git a/education/education/doctype/education_settings/__init__.py b/education/education/doctype/education_settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/education_settings/education_settings.js b/education/education/doctype/education_settings/education_settings.js new file mode 100644 index 0000000..a09dce6 --- /dev/null +++ b/education/education/doctype/education_settings/education_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Education Settings', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/education_settings/education_settings.json b/education/education/doctype/education_settings/education_settings.json new file mode 100644 index 0000000..0e548db --- /dev/null +++ b/education/education/doctype/education_settings/education_settings.json @@ -0,0 +1,149 @@ +{ + "actions": [], + "creation": "2017-04-05 13:33:04.519313", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "current_academic_year", + "current_academic_term", + "attendance_freeze_date", + "column_break_4", + "validate_batch", + "validate_course", + "academic_term_reqd", + "user_creation_skip", + "section_break_7", + "instructor_created_by", + "web_academy_settings_section", + "enable_lms", + "portal_title", + "description" + ], + "fields": [ + { + "fieldname": "current_academic_year", + "fieldtype": "Link", + "label": "Current Academic Year", + "options": "Academic Year" + }, + { + "fieldname": "current_academic_term", + "fieldtype": "Link", + "label": "Current Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "attendance_freeze_date", + "fieldtype": "Date", + "label": "Attendance Freeze Date" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.", + "fieldname": "validate_batch", + "fieldtype": "Check", + "label": "Validate Batch for Students in Student Group" + }, + { + "default": "0", + "description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.", + "fieldname": "validate_course", + "fieldtype": "Check", + "label": "Validate Enrolled Course for Students in Student Group" + }, + { + "default": "0", + "description": "If enabled, field Academic Term will be Mandatory in Program Enrollment Tool.", + "fieldname": "academic_term_reqd", + "fieldtype": "Check", + "label": "Make Academic Term Mandatory" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "default": "Full Name", + "fieldname": "instructor_created_by", + "fieldtype": "Select", + "label": "Instructor Records to be created by", + "options": "Full Name\nNaming Series\nEmployee Number" + }, + { + "fieldname": "web_academy_settings_section", + "fieldtype": "Section Break", + "label": "Learning Management System Settings" + }, + { + "depends_on": "eval: doc.enable_lms", + "fieldname": "portal_title", + "fieldtype": "Data", + "label": "Learning Management System Title" + }, + { + "depends_on": "eval: doc.enable_lms", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "default": "0", + "fieldname": "enable_lms", + "fieldtype": "Check", + "label": "Enable Learning Management System" + }, + { + "default": "0", + "description": "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.", + "fieldname": "user_creation_skip", + "fieldtype": "Check", + "label": "Skip User creation for new Student" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-05-07 19:18:10.639356", + "modified_by": "Administrator", + "module": "Education", + "name": "Education Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Education Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Guest", + "share": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/education_settings/education_settings.py b/education/education/doctype/education_settings/education_settings.py new file mode 100644 index 0000000..f1c7963 --- /dev/null +++ b/education/education/doctype/education_settings/education_settings.py @@ -0,0 +1,55 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +import frappe +import frappe.defaults +from frappe.model.document import Document + +education_keydict = { + # "key in defaults": "key in Global Defaults" + "academic_year": "current_academic_year", + "academic_term": "current_academic_term", + "validate_batch": "validate_batch", + "validate_course": "validate_course", +} + + +class EducationSettings(Document): + def on_update(self): + """update defaults""" + for key in education_keydict: + frappe.db.set_default(key, self.get(education_keydict[key], "")) + + # clear cache + frappe.clear_cache() + + def get_defaults(self): + return frappe.defaults.get_defaults() + + def validate(self): + from frappe.custom.doctype.property_setter.property_setter import \ + make_property_setter + + if self.get("instructor_created_by") == "Naming Series": + make_property_setter( + "Instructor", + "naming_series", + "hidden", + 0, + "Check", + validate_fields_for_doctype=False, + ) + else: + make_property_setter( + "Instructor", + "naming_series", + "hidden", + 1, + "Check", + validate_fields_for_doctype=False, + ) + + +def update_website_context(context): + context["lms_enabled"] = frappe.get_cached_doc("Education Settings").enable_lms diff --git a/education/education/doctype/education_settings/test_education_settings.py b/education/education/doctype/education_settings/test_education_settings.py new file mode 100644 index 0000000..6740ad6 --- /dev/null +++ b/education/education/doctype/education_settings/test_education_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestEducationSettings(unittest.TestCase): + pass diff --git a/education/education/doctype/fee_category/__init__.py b/education/education/doctype/fee_category/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_category/fee_category.js b/education/education/doctype/fee_category/fee_category.js new file mode 100644 index 0000000..07c62d1 --- /dev/null +++ b/education/education/doctype/fee_category/fee_category.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Fee Category', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/fee_category/fee_category.json b/education/education/doctype/fee_category/fee_category.json new file mode 100644 index 0000000..c1bfa8e --- /dev/null +++ b/education/education/doctype/fee_category/fee_category.json @@ -0,0 +1,171 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:category_name", + "beta": 0, + "creation": "2015-09-16 13:01:10.448734", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "category_name", + "fieldtype": "Data", + "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": "Name", + "length": 0, + "no_copy": 0, + "oldfieldname": "earning_name", + "oldfieldtype": "Data", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "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": "Description", + "length": 0, + "no_copy": 0, + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "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, + "unique": 0, + "width": "300px" + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-flag", + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "menu_index": 0, + "modified": "2017-11-10 18:56:33.824534", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Category", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 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": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 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": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "search_fields": "description", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/fee_category/fee_category.py b/education/education/doctype/fee_category/fee_category.py new file mode 100644 index 0000000..c384aa1 --- /dev/null +++ b/education/education/doctype/fee_category/fee_category.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class FeeCategory(Document): + pass diff --git a/education/education/doctype/fee_category/test_fee_category.py b/education/education/doctype/fee_category/test_fee_category.py new file mode 100644 index 0000000..466bcdd --- /dev/null +++ b/education/education/doctype/fee_category/test_fee_category.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Fee Category') + + +class TestFeeCategory(unittest.TestCase): + pass diff --git a/education/education/doctype/fee_category/test_records.json b/education/education/doctype/fee_category/test_records.json new file mode 100644 index 0000000..598c1ed --- /dev/null +++ b/education/education/doctype/fee_category/test_records.json @@ -0,0 +1,11 @@ +[ + { + "category_name": "Admission Fee" + }, + { + "category_name": "Tuition Fee" + }, + { + "category_name": "Transportation Fee" + } +] \ No newline at end of file diff --git a/education/education/doctype/fee_component/__init__.py b/education/education/doctype/fee_component/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_component/fee_component.json b/education/education/doctype/fee_component/fee_component.json new file mode 100644 index 0000000..f6e13c4 --- /dev/null +++ b/education/education/doctype/fee_component/fee_component.json @@ -0,0 +1,169 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "", + "beta": 0, + "creation": "2015-09-16 13:07:27.675453", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fees_category", + "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": "Fees Category", + "length": 0, + "no_copy": 0, + "oldfieldname": "earning_name", + "oldfieldtype": "Data", + "options": "Fee Category", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "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": "Description", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "length": 0, + "no_copy": 0, + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "300px" + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-flag", + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 18:58:10.254407", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Component", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "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", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/fee_component/fee_component.py b/education/education/doctype/fee_component/fee_component.py new file mode 100644 index 0000000..b32cf7a --- /dev/null +++ b/education/education/doctype/fee_component/fee_component.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class FeeComponent(Document): + pass diff --git a/education/education/doctype/fee_schedule/__init__.py b/education/education/doctype/fee_schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_schedule/fee_schedule.js b/education/education/doctype/fee_schedule/fee_schedule.js new file mode 100644 index 0000000..31e7b2e --- /dev/null +++ b/education/education/doctype/fee_schedule/fee_schedule.js @@ -0,0 +1,133 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.provide("erpnext.accounts.dimensions"); +frappe.ui.form.on('Fee Schedule', { + setup: function(frm) { + frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account'); + frm.add_fetch('fee_structure', 'income_account', 'income_account'); + frm.add_fetch('fee_structure', 'cost_center', 'cost_center'); + }, + + company: function(frm) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + }, + + onload: function(frm) { + frm.set_query('receivable_account', function(doc) { + return { + filters: { + 'account_type': 'Receivable', + 'is_group': 0, + 'company': doc.company + } + }; + }); + + frm.set_query('income_account', function(doc) { + return { + filters: { + 'account_type': 'Income Account', + 'is_group': 0, + 'company': doc.company + } + }; + }); + + frm.set_query('student_group', 'student_groups', function() { + return { + 'program': frm.doc.program, + 'academic_term': frm.doc.academic_term, + 'academic_year': frm.doc.academic_year, + 'disabled': 0 + }; + }); + + frappe.realtime.on('fee_schedule_progress', function(data) { + if (data.reload && data.reload === 1) { + frm.reload_doc(); + } + if (data.progress) { + let progress_bar = $(cur_frm.dashboard.progress_area.body).find('.progress-bar'); + if (progress_bar) { + $(progress_bar).removeClass('progress-bar-danger').addClass('progress-bar-success progress-bar-striped'); + $(progress_bar).css('width', data.progress+'%'); + } + } + }); + + erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + }, + + refresh: function(frm) { + if (!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info && + frm.doc.fee_creation_status === 'Successful') { + var info = frm.doc.__onload.dashboard_info; + frm.dashboard.add_indicator(__('Total Collected: {0}', [format_currency(info.total_paid, + info.currency)]), 'blue'); + frm.dashboard.add_indicator(__('Total Outstanding: {0}', [format_currency(info.total_unpaid, + info.currency)]), info.total_unpaid ? 'orange' : 'green'); + } + if (frm.doc.fee_creation_status === 'In Process') { + frm.dashboard.add_progress('Fee Creation Status', '0'); + } + if (frm.doc.docstatus === 1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status === 'Failed') { + frm.add_custom_button(__('Create Fees'), function() { + frappe.call({ + method: 'create_fees', + doc: frm.doc, + callback: function() { + frm.refresh(); + } + }); + }).addClass('btn-primary');; + } + if (frm.doc.fee_creation_status === 'Successful') { + frm.add_custom_button(__('View Fees Records'), function() { + frappe.route_options = { + fee_schedule: frm.doc.name + }; + frappe.set_route('List', 'Fees'); + }); + } + + }, + + fee_structure: function(frm) { + if (frm.doc.fee_structure) { + frappe.call({ + method: 'education.education.doctype.fee_schedule.fee_schedule.get_fee_structure', + args: { + 'target_doc': frm.doc.name, + 'source_name': frm.doc.fee_structure + }, + callback: function(r) { + var doc = frappe.model.sync(r.message); + frappe.set_route('Form', doc[0].doctype, doc[0].name); + } + }); + } + } +}); + +frappe.ui.form.on('Fee Schedule Student Group', { + student_group: function(frm, cdt, cdn) { + var row = locals[cdt][cdn]; + if (row.student_group && frm.doc.academic_year) { + frappe.call({ + method: 'education.education.doctype.fee_schedule.fee_schedule.get_total_students', + args: { + 'student_group': row.student_group, + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term, + 'student_category': frm.doc.student_category + }, + callback: function(r) { + if (!r.exc) { + frappe.model.set_value(cdt, cdn, 'total_students', r.message); + } + } + }); + } + } +}) diff --git a/education/education/doctype/fee_schedule/fee_schedule.json b/education/education/doctype/fee_schedule/fee_schedule.json new file mode 100644 index 0000000..23b3212 --- /dev/null +++ b/education/education/doctype/fee_schedule/fee_schedule.json @@ -0,0 +1,327 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2017-07-18 15:21:21.527136", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "fee_structure", + "posting_date", + "due_date", + "naming_series", + "fee_creation_status", + "send_email", + "column_break_4", + "student_category", + "program", + "academic_year", + "academic_term", + "section_break_10", + "currency", + "student_groups", + "section_break_14", + "components", + "section_break_16", + "column_break_18", + "total_amount", + "grand_total", + "grand_total_in_words", + "edit_printing_settings", + "letter_head", + "column_break_32", + "select_print_heading", + "account", + "receivable_account", + "income_account", + "column_break_39", + "company", + "amended_from", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "section_break_31", + "error_log" + ], + "fields": [ + { + "fieldname": "fee_structure", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Fee Structure", + "options": "Fee Structure", + "reqd": 1 + }, + { + "fieldname": "due_date", + "fieldtype": "Date", + "label": "Due Date", + "reqd": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "no_copy": 1, + "options": "EDU-FSH-.YYYY.-" + }, + { + "fieldname": "fee_creation_status", + "fieldtype": "Select", + "label": "Fee Creation Status", + "no_copy": 1, + "options": "\nIn Process\nFailed\nSuccessful", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "0", + "fieldname": "send_email", + "fieldtype": "Check", + "label": "Send Payment Request Email" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category", + "read_only": 1 + }, + { + "fieldname": "program", + "fieldtype": "Link", + "label": "Program", + "options": "Program", + "read_only": 1 + }, + { + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "section_break_10", + "fieldtype": "Section Break" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Currency", + "options": "Currency", + "read_only": 1 + }, + { + "fieldname": "student_groups", + "fieldtype": "Table", + "options": "Fee Schedule Student Group", + "reqd": 1 + }, + { + "fieldname": "section_break_14", + "fieldtype": "Section Break", + "label": "Fee Breakup for each student", + "read_only": 1 + }, + { + "fieldname": "components", + "fieldtype": "Table", + "options": "Fee Component", + "read_only": 1 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "total_amount", + "fieldtype": "Currency", + "label": "Total Amount per Student", + "read_only": 1 + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "label": "Grand Total", + "read_only": 1 + }, + { + "fieldname": "grand_total_in_words", + "fieldtype": "Data", + "label": "In Words", + "length": 240, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "edit_printing_settings", + "fieldtype": "Section Break", + "label": "Printing Settings" + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "fieldname": "column_break_32", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "account", + "fieldtype": "Section Break", + "label": "Accounting" + }, + { + "fieldname": "receivable_account", + "fieldtype": "Link", + "label": "Receivable Account", + "options": "Account" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Institution", + "options": "Company" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Fee Schedule", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "depends_on": "error_log", + "fieldname": "section_break_31", + "fieldtype": "Section Break", + "label": "Error Log" + }, + { + "fieldname": "error_log", + "fieldtype": "Read Only", + "label": "Error Log" + }, + { + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "reqd": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-07-18 05:11:49.905457", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Schedule", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/fee_schedule/fee_schedule.py b/education/education/doctype/fee_schedule/fee_schedule.py new file mode 100644 index 0000000..7bb8f1c --- /dev/null +++ b/education/education/doctype/fee_schedule/fee_schedule.py @@ -0,0 +1,188 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +import erpnext +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, cstr, flt, money_in_words +from frappe.utils.background_jobs import enqueue + + +class FeeSchedule(Document): + def onload(self): + info = self.get_dashboard_info() + self.set_onload("dashboard_info", info) + + def get_dashboard_info(self): + info = { + "total_paid": 0, + "total_unpaid": 0, + "currency": erpnext.get_company_currency(self.company), + } + + fees_amount = frappe.db.sql( + """select sum(grand_total), sum(outstanding_amount) from tabFees + where fee_schedule=%s and docstatus=1""", + (self.name), + ) + + if fees_amount: + info["total_paid"] = flt(fees_amount[0][0]) - flt(fees_amount[0][1]) + info["total_unpaid"] = flt(fees_amount[0][1]) + + return info + + def validate(self): + self.calculate_total_and_program() + + def calculate_total_and_program(self): + no_of_students = 0 + for d in self.student_groups: + # if not d.total_students: + d.total_students = get_total_students( + d.student_group, self.academic_year, self.academic_term, self.student_category + ) + no_of_students += cint(d.total_students) + + # validate the program of fee structure and student groups + student_group_program = frappe.db.get_value( + "Student Group", d.student_group, "program" + ) + if self.program and student_group_program and self.program != student_group_program: + frappe.msgprint( + _("Program in the Fee Structure and Student Group {0} are different.").format( + d.student_group + ) + ) + self.grand_total = no_of_students * self.total_amount + self.grand_total_in_words = money_in_words(self.grand_total) + + @frappe.whitelist() + def create_fees(self): + self.db_set("fee_creation_status", "In Process") + frappe.publish_realtime( + "fee_schedule_progress", {"progress": "0", "reload": 1}, user=frappe.session.user + ) + + total_records = sum([int(d.total_students) for d in self.student_groups]) + if total_records > 10: + frappe.msgprint( + _( + """Fee records will be created in the background. + In case of any error the error message will be updated in the Schedule.""" + ) + ) + enqueue( + generate_fee, + queue="default", + timeout=6000, + event="generate_fee", + fee_schedule=self.name, + ) + else: + generate_fee(self.name) + + +def generate_fee(fee_schedule): + doc = frappe.get_doc("Fee Schedule", fee_schedule) + error = False + total_records = sum([int(d.total_students) for d in doc.student_groups]) + created_records = 0 + + if not total_records: + frappe.throw(_("Please setup Students under Student Groups")) + + for d in doc.student_groups: + students = get_students( + d.student_group, doc.academic_year, doc.academic_term, doc.student_category + ) + for student in students: + try: + fees_doc = get_mapped_doc( + "Fee Schedule", + fee_schedule, + {"Fee Schedule": {"doctype": "Fees", "field_map": {"name": "Fee Schedule"}}}, + ) + fees_doc.posting_date = doc.posting_date + fees_doc.student = student.student + fees_doc.student_name = student.student_name + fees_doc.program = student.program + fees_doc.student_batch = student.student_batch_name + fees_doc.send_payment_request = doc.send_email + fees_doc.save() + fees_doc.submit() + created_records += 1 + frappe.publish_realtime( + "fee_schedule_progress", + {"progress": str(int(created_records * 100 / total_records))}, + user=frappe.session.user, + ) + + except Exception as e: + error = True + err_msg = ( + frappe.local.message_log and "\n\n".join(frappe.local.message_log) or cstr(e) + ) + + if error: + frappe.db.rollback() + frappe.db.set_value("Fee Schedule", fee_schedule, "fee_creation_status", "Failed") + frappe.db.set_value("Fee Schedule", fee_schedule, "error_log", err_msg) + + else: + frappe.db.set_value("Fee Schedule", fee_schedule, "fee_creation_status", "Successful") + frappe.db.set_value("Fee Schedule", fee_schedule, "error_log", None) + + frappe.publish_realtime( + "fee_schedule_progress", {"progress": "100", "reload": 1}, user=frappe.session.user + ) + + +def get_students( + student_group, academic_year, academic_term=None, student_category=None +): + conditions = "" + if student_category: + conditions = " and pe.student_category={}".format(frappe.db.escape(student_category)) + if academic_term: + conditions += " and pe.academic_term={}".format(frappe.db.escape(academic_term)) + + students = frappe.db.sql( + """ + select pe.student, pe.student_name, pe.program, pe.student_batch_name + from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe + where + pe.docstatus = 1 and pe.student = sgs.student and pe.academic_year = %s + and sgs.parent = %s and sgs.active = 1 + {conditions} + """.format( + conditions=conditions + ), + (academic_year, student_group), + as_dict=1, + ) + return students + + +@frappe.whitelist() +def get_total_students( + student_group, academic_year, academic_term=None, student_category=None +): + total_students = get_students( + student_group, academic_year, academic_term, student_category + ) + return len(total_students) + + +@frappe.whitelist() +def get_fee_structure(source_name, target_doc=None): + fee_request = get_mapped_doc( + "Fee Structure", + source_name, + {"Fee Structure": {"doctype": "Fee Schedule"}}, + ignore_permissions=True, + ) + return fee_request diff --git a/education/education/doctype/fee_schedule/fee_schedule_dashboard.py b/education/education/doctype/fee_schedule/fee_schedule_dashboard.py new file mode 100644 index 0000000..f525f61 --- /dev/null +++ b/education/education/doctype/fee_schedule/fee_schedule_dashboard.py @@ -0,0 +1,6 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + + +def get_data(): + return {"fieldname": "fee_schedule", "transactions": [{"items": ["Fees"]}]} diff --git a/education/education/doctype/fee_schedule/fee_schedule_list.js b/education/education/doctype/fee_schedule/fee_schedule_list.js new file mode 100644 index 0000000..3039c51 --- /dev/null +++ b/education/education/doctype/fee_schedule/fee_schedule_list.js @@ -0,0 +1,14 @@ +frappe.listview_settings['Fee Schedule'] = { + add_fields: ["fee_creation_status", "due_date", "grand_total"], + get_indicator: function(doc) { + if (doc.fee_creation_status=="Successful") { + return [__("Fee Created"), "blue", "fee_creation_status,=,Successful"]; + } else if(doc.fee_creation_status == "In Process") { + return [__("Creating Fees"), "orange", "fee_creation_status,=,In Process"]; + } else if(doc.fee_creation_status == "Failed") { + return [__("Fee Creation Failed"), "red", "fee_creation_status,=,Failed"]; + } else { + return [__("Fee Creation Pending"), "green", "fee_creation_status,=,"]; + } + } +}; diff --git a/education/education/doctype/fee_schedule/test_fee_schedule.py b/education/education/doctype/fee_schedule/test_fee_schedule.py new file mode 100644 index 0000000..ad27e1d --- /dev/null +++ b/education/education/doctype/fee_schedule/test_fee_schedule.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestFeeSchedule(unittest.TestCase): + pass diff --git a/education/education/doctype/fee_schedule_program/__init__.py b/education/education/doctype/fee_schedule_program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_schedule_program/fee_schedule_program.json b/education/education/doctype/fee_schedule_program/fee_schedule_program.json new file mode 100644 index 0000000..f644dc2 --- /dev/null +++ b/education/education/doctype/fee_schedule_program/fee_schedule_program.json @@ -0,0 +1,142 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-03-23 17:46:55.712169", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "program", + "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": "Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_batch", + "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": "Student Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_students", + "fieldtype": "Data", + "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": "Total Students", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:37:57.763134", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Schedule Program", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/fee_schedule_program/fee_schedule_program.py b/education/education/doctype/fee_schedule_program/fee_schedule_program.py new file mode 100644 index 0000000..07d8fb1 --- /dev/null +++ b/education/education/doctype/fee_schedule_program/fee_schedule_program.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class FeeScheduleProgram(Document): + pass diff --git a/education/education/doctype/fee_schedule_student_group/__init__.py b/education/education/doctype/fee_schedule_student_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.json b/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.json new file mode 100644 index 0000000..d4e0acb --- /dev/null +++ b/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.json @@ -0,0 +1,109 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-03-23 17:55:52.476822", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 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": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_students", + "fieldtype": "Data", + "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": "Total Students", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:38:06.535706", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Schedule Student Group", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.py b/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.py new file mode 100644 index 0000000..2d663f8 --- /dev/null +++ b/education/education/doctype/fee_schedule_student_group/fee_schedule_student_group.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class FeeScheduleStudentGroup(Document): + pass diff --git a/education/education/doctype/fee_structure/__init__.py b/education/education/doctype/fee_structure/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/fee_structure/fee_structure.js b/education/education/doctype/fee_structure/fee_structure.js new file mode 100644 index 0000000..e54035a --- /dev/null +++ b/education/education/doctype/fee_structure/fee_structure.js @@ -0,0 +1,72 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.provide("erpnext.accounts.dimensions"); + +frappe.ui.form.on('Fee Structure', { + setup: function(frm) { + frm.add_fetch('company', 'default_receivable_account', 'receivable_account'); + frm.add_fetch('company', 'default_income_account', 'income_account'); + frm.add_fetch('company', 'cost_center', 'cost_center'); + }, + + company: function(frm) { + erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + }, + + onload: function(frm) { + frm.set_query('academic_term', function() { + return { + 'filters': { + 'academic_year': frm.doc.academic_year + } + }; + }); + + frm.set_query('receivable_account', function(doc) { + return { + filters: { + 'account_type': 'Receivable', + 'is_group': 0, + 'company': doc.company + } + }; + }); + frm.set_query('income_account', function(doc) { + return { + filters: { + 'account_type': 'Income Account', + 'is_group': 0, + 'company': doc.company + } + }; + }); + + erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); + }, + + refresh: function(frm) { + if (frm.doc.docstatus === 1) { + frm.add_custom_button(__('Create Fee Schedule'), function() { + frm.events.make_fee_schedule(frm); + }).addClass('btn-primary'); + } + }, + + make_fee_schedule: function(frm) { + frappe.model.open_mapped_doc({ + method: 'education.education.doctype.fee_structure.fee_structure.make_fee_schedule', + frm: frm + }); + } +}); + +frappe.ui.form.on('Fee Component', { + amount: function(frm) { + var total_amount = 0; + for (var i=0;i 0) { + frm.add_custom_button(__('Accounting Ledger'), function() { + frappe.route_options = { + voucher_no: frm.doc.name, + from_date: frm.doc.posting_date, + to_date: moment(frm.doc.modified).format('YYYY-MM-DD'), + company: frm.doc.company, + group_by: '', + show_cancelled_entries: frm.doc.docstatus === 2 + }; + frappe.set_route("query-report", "General Ledger"); + }, __("View")); + frm.add_custom_button(__("Payments"), function() { + frappe.set_route("List", "Payment Entry", {"Payment Entry Reference.reference_name": frm.doc.name}); + }, __("View")); + } + if(frm.doc.docstatus===1 && frm.doc.outstanding_amount>0) { + frm.add_custom_button(__("Payment Request"), function() { + frm.events.make_payment_request(frm); + }, __('Create')); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + if(frm.doc.docstatus===1 && frm.doc.outstanding_amount!=0) { + frm.add_custom_button(__("Payment"), function() { + frm.events.make_payment_entry(frm); + }, __('Create')); + frm.page.set_inner_btn_group_as_primary(__('Create')); + } + }, + + student: function(frm) { + if (frm.doc.student) { + frappe.call({ + method:"education.education.api.get_current_enrollment", + args: { + "student": frm.doc.student, + "academic_year": frm.doc.academic_year + }, + callback: function(r) { + if(r){ + $.each(r.message, function(i, d) { + frm.set_value(i,d); + }); + } + } + }); + } + }, + + make_payment_request: function(frm) { + if (!frm.doc.student_email) { + frappe.msgprint(__("Please set the Email ID for the Student to send the Payment Request")); + } else { + frappe.call({ + method:"erpnext.accounts.doctype.payment_request.payment_request.make_payment_request", + args: { + "dt": frm.doc.doctype, + "dn": frm.doc.name, + "party_type": "Student", + "party": frm.doc.student, + "recipient_id": frm.doc.student_email + }, + callback: function(r) { + if(!r.exc){ + var doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + } + }); + } + }, + + make_payment_entry: function(frm) { + return frappe.call({ + method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", + args: { + "dt": frm.doc.doctype, + "dn": frm.doc.name, + "party_type": "Student", + "payment_type": "Receive", + }, + callback: function(r) { + var doc = frappe.model.sync(r.message); + frappe.set_route("Form", doc[0].doctype, doc[0].name); + } + }); + }, + + set_posting_time: function(frm) { + frm.refresh(); + }, + + academic_term: function() { + frappe.ui.form.trigger("Fees", "program"); + }, + + fee_structure: function(frm) { + frm.set_value("components" ,""); + if (frm.doc.fee_structure) { + frappe.call({ + method: "education.education.api.get_fee_components", + args: { + "fee_structure": frm.doc.fee_structure + }, + callback: function(r) { + if (r.message) { + $.each(r.message, function(i, d) { + var row = frappe.model.add_child(frm.doc, "Fee Component", "components"); + row.fees_category = d.fees_category; + row.description = d.description; + row.amount = d.amount; + }); + } + refresh_field("components"); + frm.trigger("calculate_total_amount"); + } + }); + } + }, + + calculate_total_amount: function(frm) { + var grand_total = 0; + for(var i=0;i 0: + self.indicator_color = "orange" + self.indicator_title = _("Unpaid") + else: + self.indicator_color = "green" + self.indicator_title = _("Paid") + + def validate(self): + self.calculate_total() + self.set_missing_accounts_and_fields() + + def set_missing_accounts_and_fields(self): + if not self.company: + self.company = frappe.defaults.get_defaults().company + if not self.currency: + self.currency = erpnext.get_company_currency(self.company) + if not (self.receivable_account and self.income_account and self.cost_center): + accounts_details = frappe.get_all( + "Company", + fields=["default_receivable_account", "default_income_account", "cost_center"], + filters={"name": self.company}, + )[0] + if not self.receivable_account: + self.receivable_account = accounts_details.default_receivable_account + if not self.income_account: + self.income_account = accounts_details.default_income_account + if not self.cost_center: + self.cost_center = accounts_details.cost_center + if not self.student_email: + self.student_email = self.get_student_emails() + + def get_student_emails(self): + student_emails = frappe.db.sql_list( + """ + select g.email_address + from `tabGuardian` g, `tabStudent Guardian` sg + where g.name = sg.guardian and sg.parent = %s and sg.parenttype = 'Student' + and ifnull(g.email_address, '')!='' + """, + self.student, + ) + + student_email_id = frappe.db.get_value("Student", self.student, "student_email_id") + if student_email_id: + student_emails.append(student_email_id) + if student_emails: + return ", ".join(list(set(student_emails))) + else: + return None + + def calculate_total(self): + """Calculates total amount.""" + self.grand_total = 0 + for d in self.components: + self.grand_total += d.amount + self.outstanding_amount = self.grand_total + self.grand_total_in_words = money_in_words(self.grand_total) + + def on_submit(self): + + self.make_gl_entries() + + if self.send_payment_request and self.student_email: + pr = make_payment_request( + party_type="Student", + party=self.student, + dt="Fees", + dn=self.name, + recipient_id=self.student_email, + submit_doc=True, + use_dummy_message=True, + ) + frappe.msgprint( + _("Payment request {0} created").format(getlink("Payment Request", pr.name)) + ) + + def on_cancel(self): + self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry") + make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) + # frappe.db.set(self, 'status', 'Cancelled') + + def make_gl_entries(self): + if not self.grand_total: + return + student_gl_entries = self.get_gl_dict( + { + "account": self.receivable_account, + "party_type": "Student", + "party": self.student, + "against": self.income_account, + "debit": self.grand_total, + "debit_in_account_currency": self.grand_total, + "against_voucher": self.name, + "against_voucher_type": self.doctype, + }, + item=self, + ) + + fee_gl_entry = self.get_gl_dict( + { + "account": self.income_account, + "against": self.student, + "credit": self.grand_total, + "credit_in_account_currency": self.grand_total, + "cost_center": self.cost_center, + }, + item=self, + ) + + from erpnext.accounts.general_ledger import make_gl_entries + + make_gl_entries( + [student_gl_entries, fee_gl_entry], + cancel=(self.docstatus == 2), + update_outstanding="Yes", + merge_entries=False, + ) + + +def get_fee_list( + doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified" +): + user = frappe.session.user + student = frappe.db.sql( + "select name from `tabStudent` where student_email_id= %s", user + ) + if student: + return frappe.db.sql( + """ + select name, program, due_date, grand_total - outstanding_amount as paid_amount, + outstanding_amount, grand_total, currency + from `tabFees` + where student= %s and docstatus=1 + order by due_date asc limit {0} , {1}""".format( + limit_start, limit_page_length + ), + student, + as_dict=True, + ) + + +def get_list_context(context=None): + return { + "show_sidebar": True, + "show_search": True, + "no_breadcrumbs": True, + "title": _("Fees"), + "get_list": get_fee_list, + "row_template": "templates/includes/fee/fee_row.html", + } diff --git a/education/education/doctype/fees/fees_list.js b/education/education/doctype/fees/fees_list.js new file mode 100644 index 0000000..ee8e1e3 --- /dev/null +++ b/education/education/doctype/fees/fees_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings['Fees'] = { + add_fields: ["grand_total", "outstanding_amount", "due_date"], + get_indicator: function(doc) { + if(flt(doc.outstanding_amount)==0) { + return [__("Paid"), "green", "outstanding_amount,=,0"]; + } else if (flt(doc.outstanding_amount) > 0 && doc.due_date >= frappe.datetime.get_today()) { + return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"]; + } else if (flt(doc.outstanding_amount) > 0 && doc.due_date < frappe.datetime.get_today()) { + return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"]; + } + } +}; diff --git a/education/education/doctype/fees/test_fees.py b/education/education/doctype/fees/test_fees.py new file mode 100644 index 0000000..1710afc --- /dev/null +++ b/education/education/doctype/fees/test_fees.py @@ -0,0 +1,60 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe +from frappe.utils import nowdate +from frappe.utils.make_random import get_random + +from education.education.doctype.program.test_program import \ + make_program_and_linked_courses + +test_dependencies = ["Company"] + + +class TestFees(unittest.TestCase): + def test_fees(self): + student = get_random("Student") + program = make_program_and_linked_courses( + "_Test Program 1", ["_Test Course 1", "_Test Course 2"] + ) + fee = frappe.new_doc("Fees") + fee.posting_date = nowdate() + fee.due_date = nowdate() + fee.student = student + fee.receivable_account = "_Test Receivable - _TC" + fee.income_account = "Sales - _TC" + fee.cost_center = "_Test Cost Center - _TC" + fee.company = "_Test Company" + fee.program = program.name + + fee.extend( + "components", + [ + {"fees_category": "Tuition Fee", "amount": 40000}, + {"fees_category": "Transportation Fee", "amount": 10000}, + ], + ) + fee.save() + fee.submit() + + gl_entries = frappe.db.sql( + """ + select account, posting_date, party_type, party, cost_center, fiscal_year, voucher_type, + voucher_no, against_voucher_type, against_voucher, cost_center, company, credit, debit + from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", + ("Fees", fee.name), + as_dict=True, + ) + + if gl_entries[0].account == "_Test Receivable - _TC": + self.assertEqual(gl_entries[0].debit, 50000) + self.assertEqual(gl_entries[0].credit, 0) + self.assertEqual(gl_entries[1].debit, 0) + self.assertEqual(gl_entries[1].credit, 50000) + else: + self.assertEqual(gl_entries[0].credit, 50000) + self.assertEqual(gl_entries[0].debit, 0) + self.assertEqual(gl_entries[1].credit, 0) + self.assertEqual(gl_entries[1].debit, 50000) diff --git a/education/education/doctype/grading_scale/__init__.py b/education/education/doctype/grading_scale/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/grading_scale/grading_scale.js b/education/education/doctype/grading_scale/grading_scale.js new file mode 100644 index 0000000..bc8672e --- /dev/null +++ b/education/education/doctype/grading_scale/grading_scale.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Grading Scale', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/grading_scale/grading_scale.json b/education/education/doctype/grading_scale/grading_scale.json new file mode 100644 index 0000000..fb1e2c7 --- /dev/null +++ b/education/education/doctype/grading_scale/grading_scale.json @@ -0,0 +1,226 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:grading_scale_name", + "beta": 0, + "creation": "2016-08-26 03:06:53.922972", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "grading_scale_name", + "fieldtype": "Data", + "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": "Grading Scale Name", + "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": 1 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "grading_intervals_section", + "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, + "label": "Grading Scale Intervals", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "intervals", + "fieldtype": "Table", + "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": "Intervals", + "length": 0, + "no_copy": 0, + "options": "Grading Scale Interval", + "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, + "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": "Grading Scale", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "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": "Description", + "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 + } + ], + "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-30 00:34:03.368432", + "modified_by": "Administrator", + "module": "Education", + "name": "Grading Scale", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "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, + "write": 1 + } + ], + "quick_entry": 1, + "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": "", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/grading_scale/grading_scale.py b/education/education/doctype/grading_scale/grading_scale.py new file mode 100644 index 0000000..d0e071e --- /dev/null +++ b/education/education/doctype/grading_scale/grading_scale.py @@ -0,0 +1,20 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint + + +class GradingScale(Document): + def validate(self): + thresholds = [] + for d in self.intervals: + if d.threshold in thresholds: + frappe.throw(_("Treshold {0}% appears more than once").format(d.threshold)) + else: + thresholds.append(cint(d.threshold)) + if 0 not in thresholds: + frappe.throw(_("Please define grade for Threshold 0%")) diff --git a/education/education/doctype/grading_scale/grading_scale_dashboard.py b/education/education/doctype/grading_scale/grading_scale_dashboard.py new file mode 100644 index 0000000..6995666 --- /dev/null +++ b/education/education/doctype/grading_scale/grading_scale_dashboard.py @@ -0,0 +1,12 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "grading_scale", + "non_standard_fieldnames": {"Course": "default_grading_scale"}, + "transactions": [ + {"label": _("Course"), "items": ["Course"]}, + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + ], + } diff --git a/education/education/doctype/grading_scale/test_grading_scale.py b/education/education/doctype/grading_scale/test_grading_scale.py new file mode 100644 index 0000000..d1997a7 --- /dev/null +++ b/education/education/doctype/grading_scale/test_grading_scale.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Grading Scale') + + +class TestGradingScale(unittest.TestCase): + pass diff --git a/education/education/doctype/grading_scale/test_records.json b/education/education/doctype/grading_scale/test_records.json new file mode 100644 index 0000000..72b6954 --- /dev/null +++ b/education/education/doctype/grading_scale/test_records.json @@ -0,0 +1,19 @@ +[ + { + "grading_scale_name": "_Test Grading Scale", + "intervals": [ + { + "grade_code": "A", + "threshold": 75 + }, + { + "grade_code": "B", + "threshold": 50 + }, + { + "grade_code": "C", + "threshold": 0 + } + ] + } +] \ No newline at end of file diff --git a/education/education/doctype/grading_scale_interval/__init__.py b/education/education/doctype/grading_scale_interval/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/grading_scale_interval/grading_scale_interval.json b/education/education/doctype/grading_scale_interval/grading_scale_interval.json new file mode 100644 index 0000000..5574e1d --- /dev/null +++ b/education/education/doctype/grading_scale_interval/grading_scale_interval.json @@ -0,0 +1,133 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-08-26 03:11:09.591049", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "grade_code", + "fieldtype": "Data", + "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": "Grade Code", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "threshold", + "fieldtype": "Percent", + "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": "Threshold", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "1", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "grade_description", + "fieldtype": "Small Text", + "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": "Grade Description", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:08:48.083084", + "modified_by": "Administrator", + "module": "Education", + "name": "Grading Scale Interval", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/grading_scale_interval/grading_scale_interval.py b/education/education/doctype/grading_scale_interval/grading_scale_interval.py new file mode 100644 index 0000000..0f2b791 --- /dev/null +++ b/education/education/doctype/grading_scale_interval/grading_scale_interval.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class GradingScaleInterval(Document): + pass diff --git a/education/education/doctype/guardian/__init__.py b/education/education/doctype/guardian/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/guardian/guardian.js b/education/education/doctype/guardian/guardian.js new file mode 100644 index 0000000..55c6027 --- /dev/null +++ b/education/education/doctype/guardian/guardian.js @@ -0,0 +1,20 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Guardian', { + refresh: function(frm) { + if(!frm.doc.user && !frm.is_new()) { + frm.add_custom_button(__("Invite as User"), function() { + return frappe.call({ + method: "education.education.doctype.guardian.guardian.invite_guardian", + args: { + guardian: frm.doc.name + }, + callback: function(r) { + frm.set_value("user", r.message); + } + }); + }); + } + } +}); diff --git a/education/education/doctype/guardian/guardian.json b/education/education/doctype/guardian/guardian.json new file mode 100644 index 0000000..7b8168e --- /dev/null +++ b/education/education/doctype/guardian/guardian.json @@ -0,0 +1,580 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "EDU-GRD-.YYYY.-.#####", + "beta": 0, + "creation": "2016-07-21 15:32:51.163292", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "guardian_name", + "fieldtype": "Data", + "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": "Guardian Name", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "email_address", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Email Address", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mobile_number", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Mobile Number", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "alternate_number", + "fieldtype": "Data", + "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": "Alternate Number", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "date_of_birth", + "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 of Birth", + "length": 0, + "no_copy": 1, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "user", + "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": "User Id", + "length": 0, + "no_copy": 0, + "options": "User", + "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_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, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "education", + "fieldtype": "Data", + "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": "Education", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "occupation", + "fieldtype": "Data", + "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": "Occupation", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Data", + "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": "Designation", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "work_address", + "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": "Work Address", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Image", + "length": 0, + "no_copy": 1, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_13", + "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, + "label": "Guardian Of ", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "students", + "fieldtype": "Table", + "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": "Students", + "length": 0, + "no_copy": 0, + "options": "Guardian Student", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "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, + "label": "Guardian Interests", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "interests", + "fieldtype": "Table", + "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": "Interests", + "length": 0, + "no_copy": 0, + "options": "Guardian Interest", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_field": "image", + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-08-21 16:15:54.050317", + "modified_by": "Administrator", + "module": "Education", + "name": "Guardian", + "name_case": "", + "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, + "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": "guardian_name", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/guardian/guardian.py b/education/education/doctype/guardian/guardian.py new file mode 100644 index 0000000..4784b9e --- /dev/null +++ b/education/education/doctype/guardian/guardian.py @@ -0,0 +1,61 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils.csvutils import getlink + + +class Guardian(Document): + def __setup__(self): + self.onload() + + def onload(self): + """Load Students for quick view""" + self.load_students() + + def load_students(self): + """Load `students` from the database""" + self.students = [] + students = frappe.get_all( + "Student Guardian", filters={"guardian": self.name}, fields=["parent"] + ) + for student in students: + self.append( + "students", + { + "student": student.parent, + "student_name": frappe.db.get_value("Student", student.parent, "title"), + }, + ) + + def validate(self): + self.students = [] + + +@frappe.whitelist() +def invite_guardian(guardian): + guardian_doc = frappe.get_doc("Guardian", guardian) + if not guardian_doc.email_address: + frappe.throw(_("Please set Email Address")) + else: + guardian_as_user = frappe.get_value("User", dict(email=guardian_doc.email_address)) + if guardian_as_user: + frappe.msgprint( + _("User {0} already exists").format(getlink("User", guardian_as_user)) + ) + return guardian_as_user + else: + user = frappe.get_doc( + { + "doctype": "User", + "first_name": guardian_doc.guardian_name, + "email": guardian_doc.email_address, + "user_type": "Website User", + "send_welcome_email": 1, + } + ).insert(ignore_permissions=True) + frappe.msgprint(_("User {0} created").format(getlink("User", user.name))) + return user.name diff --git a/education/education/doctype/guardian/test_guardian.py b/education/education/doctype/guardian/test_guardian.py new file mode 100644 index 0000000..4a65b03 --- /dev/null +++ b/education/education/doctype/guardian/test_guardian.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Guardian') + + +class TestGuardian(unittest.TestCase): + pass diff --git a/education/education/doctype/guardian_interest/__init__.py b/education/education/doctype/guardian_interest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/guardian_interest/guardian_interest.json b/education/education/doctype/guardian_interest/guardian_interest.json new file mode 100644 index 0000000..6129959 --- /dev/null +++ b/education/education/doctype/guardian_interest/guardian_interest.json @@ -0,0 +1,72 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-07-25 07:19:55.113871", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "interest", + "fieldtype": "Data", + "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": "Interest", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:10:22.333454", + "modified_by": "Administrator", + "module": "Education", + "name": "Guardian Interest", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} diff --git a/education/education/doctype/guardian_interest/guardian_interest.py b/education/education/doctype/guardian_interest/guardian_interest.py new file mode 100644 index 0000000..49b5a9c --- /dev/null +++ b/education/education/doctype/guardian_interest/guardian_interest.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class GuardianInterest(Document): + pass diff --git a/education/education/doctype/guardian_student/__init__.py b/education/education/doctype/guardian_student/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/guardian_student/guardian_student.json b/education/education/doctype/guardian_student/guardian_student.json new file mode 100644 index 0000000..2519a54 --- /dev/null +++ b/education/education/doctype/guardian_student/guardian_student.json @@ -0,0 +1,132 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-01-31 11:53:21.580099", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "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": 0, + "in_list_view": 1, + "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": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Student Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:10:15.786362", + "modified_by": "Administrator", + "module": "Education", + "name": "Guardian Student", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/guardian_student/guardian_student.py b/education/education/doctype/guardian_student/guardian_student.py new file mode 100644 index 0000000..1c3e8b9 --- /dev/null +++ b/education/education/doctype/guardian_student/guardian_student.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class GuardianStudent(Document): + pass diff --git a/education/education/doctype/instructor/__init__.py b/education/education/doctype/instructor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/instructor/instructor.js b/education/education/doctype/instructor/instructor.js new file mode 100644 index 0000000..2efc665 --- /dev/null +++ b/education/education/doctype/instructor/instructor.js @@ -0,0 +1,64 @@ +cur_frm.add_fetch("employee", "department", "department"); +cur_frm.add_fetch("employee", "image", "image"); + +frappe.ui.form.on("Instructor", { + employee: function(frm) { + if (!frm.doc.employee) return; + frappe.db.get_value("Employee", {name: frm.doc.employee}, "company", (d) => { + frm.set_query("department", function() { + return { + "filters": { + "company": d.company, + } + }; + }); + frm.set_query("department", "instructor_log", function() { + return { + "filters": { + "company": d.company, + } + }; + }); + }); + }, + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("As Examiner"), function() { + frappe.new_doc("Assessment Plan", { + examiner: frm.doc.name + }); + }, __("Assessment Plan")); + frm.add_custom_button(__("As Supervisor"), function() { + frappe.new_doc("Assessment Plan", { + supervisor: frm.doc.name + }); + }, __("Assessment Plan")); + } + frm.set_query("employee", function(doc) { + return { + "filters": { + "department": doc.department, + } + }; + }); + + frm.set_query("academic_term", "instructor_log", function(_doc, cdt, cdn) { + let d = locals[cdt][cdn]; + return { + filters: { + "academic_year": d.academic_year + } + }; + }); + + frm.set_query("course", "instructor_log", function(_doc, cdt, cdn) { + let d = locals[cdt][cdn]; + return { + query: "education.education.doctype.program_enrollment.program_enrollment.get_program_courses", + filters: { + "program": d.program + } + }; + }); + } +}); diff --git a/education/education/doctype/instructor/instructor.json b/education/education/doctype/instructor/instructor.json new file mode 100644 index 0000000..a417391 --- /dev/null +++ b/education/education/doctype/instructor/instructor.json @@ -0,0 +1,125 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2015-11-04 15:56:30.004034", + "doctype": "DocType", + "document_type": "Other", + "engine": "InnoDB", + "field_order": [ + "instructor_name", + "employee", + "gender", + "column_break_5", + "status", + "naming_series", + "department", + "image", + "log_details", + "instructor_log" + ], + "fields": [ + { + "fieldname": "instructor_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Instructor Name", + "reqd": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "EDU-INS-.YYYY.-", + "set_only_once": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "log_details", + "fieldtype": "Section Break", + "label": "Instructor Log" + }, + { + "fieldname": "instructor_log", + "fieldtype": "Table", + "label": "Instructor Log", + "options": "Instructor Log" + }, + { + "default": "Active", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Active\nLeft" + }, + { + "fetch_from": "employee.gender", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only_depends_on": "employee" + } + ], + "image_field": "image", + "links": [], + "modified": "2020-07-23 18:33:57.904398", + "modified_by": "Administrator", + "module": "Education", + "name": "Instructor", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor" + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Education Manager", + "set_user_permissions": 1, + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "instructor_name" +} \ No newline at end of file diff --git a/education/education/doctype/instructor/instructor.py b/education/education/doctype/instructor/instructor.py new file mode 100644 index 0000000..b6a45b3 --- /dev/null +++ b/education/education/doctype/instructor/instructor.py @@ -0,0 +1,54 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.naming import set_name_by_naming_series + + +class Instructor(Document): + def autoname(self): + naming_method = frappe.db.get_value( + "Education Settings", None, "instructor_created_by" + ) + if not naming_method: + frappe.throw( + _("Please setup Instructor Naming System in Education > Education Settings") + ) + else: + if naming_method == "Naming Series": + set_name_by_naming_series(self) + elif naming_method == "Employee Number": + if not self.employee: + frappe.throw(_("Please select Employee")) + self.name = self.employee + elif naming_method == "Full Name": + self.name = self.instructor_name + + def validate(self): + self.validate_duplicate_employee() + + def validate_duplicate_employee(self): + if self.employee and frappe.db.get_value( + "Instructor", {"employee": self.employee, "name": ["!=", self.name]}, "name" + ): + frappe.throw(_("Employee ID is linked with another instructor")) + + +def get_timeline_data(doctype, name): + """Return timeline for course schedule""" + return dict( + frappe.db.sql( + """ + SELECT unix_timestamp(`schedule_date`), count(*) + FROM `tabCourse Schedule` + WHERE + instructor=%s and + `schedule_date` > date_sub(curdate(), interval 1 year) + GROUP BY schedule_date + """, + name, + ) + ) diff --git a/education/education/doctype/instructor/instructor_dashboard.py b/education/education/doctype/instructor/instructor_dashboard.py new file mode 100644 index 0000000..920c38b --- /dev/null +++ b/education/education/doctype/instructor/instructor_dashboard.py @@ -0,0 +1,20 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "heatmap": True, + "heatmap_message": _("This is based on the course schedules of this Instructor"), + "fieldname": "instructor", + "non_standard_fieldnames": {"Assessment Plan": "supervisor"}, + "transactions": [ + { + "label": _("Course and Assessment"), + "items": ["Course Schedule", "Assessment Plan"], + }, + {"label": _("Students"), "items": ["Student Group"]}, + ], + } diff --git a/education/education/doctype/instructor/test_instructor.py b/education/education/doctype/instructor/test_instructor.py new file mode 100644 index 0000000..3c5781c --- /dev/null +++ b/education/education/doctype/instructor/test_instructor.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Instructor') + + +class TestInstructor(unittest.TestCase): + pass diff --git a/education/education/doctype/instructor/test_records.json b/education/education/doctype/instructor/test_records.json new file mode 100644 index 0000000..220d84e --- /dev/null +++ b/education/education/doctype/instructor/test_records.json @@ -0,0 +1,12 @@ +[ + { + "naming_series": "_T-Instructor-", + "employee": "_T-Employee-00001", + "instructor_name": "_Test Instructor" + }, + { + "naming_series": "_T-Instructor-", + "employee": "_T-Employee-00002", + "instructor_name": "_Test Instructor 2" + } +] diff --git a/education/education/doctype/instructor_log/__init__.py b/education/education/doctype/instructor_log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/instructor_log/instructor_log.json b/education/education/doctype/instructor_log/instructor_log.json new file mode 100644 index 0000000..5b9e1f9 --- /dev/null +++ b/education/education/doctype/instructor_log/instructor_log.json @@ -0,0 +1,88 @@ +{ + "actions": [], + "creation": "2017-12-27 08:55:52.680284", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "academic_year", + "academic_term", + "department", + "column_break_3", + "program", + "course", + "student_group", + "section_break_8", + "other_details" + ], + "fields": [ + { + "fieldname": "academic_year", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Academic Year", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "program", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Program", + "options": "Program", + "reqd": 1 + }, + { + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "Course" + }, + { + "fieldname": "student_group", + "fieldtype": "Link", + "label": "Student Group", + "options": "Student Group" + }, + { + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, + { + "fieldname": "other_details", + "fieldtype": "Small Text", + "label": "Other details" + } + ], + "istable": 1, + "links": [], + "modified": "2020-10-23 15:15:50.759657", + "modified_by": "Administrator", + "module": "Education", + "name": "Instructor Log", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/instructor_log/instructor_log.py b/education/education/doctype/instructor_log/instructor_log.py new file mode 100644 index 0000000..8e67e67 --- /dev/null +++ b/education/education/doctype/instructor_log/instructor_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class InstructorLog(Document): + pass diff --git a/education/education/doctype/options/__init__.py b/education/education/doctype/options/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/options/options.json b/education/education/doctype/options/options.json new file mode 100644 index 0000000..59deab7 --- /dev/null +++ b/education/education/doctype/options/options.json @@ -0,0 +1,107 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-10-15 14:05:28.601274", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "option", + "fieldtype": "Small Text", + "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": "Option", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_correct", + "fieldtype": "Check", + "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": "Is Correct", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-10-15 14:16:18.303156", + "modified_by": "Administrator", + "module": "Education", + "name": "Options", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/options/options.py b/education/education/doctype/options/options.py new file mode 100644 index 0000000..c6944c9 --- /dev/null +++ b/education/education/doctype/options/options.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Options(Document): + pass diff --git a/education/education/doctype/program/__init__.py b/education/education/doctype/program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program/program.js b/education/education/doctype/program/program.js new file mode 100644 index 0000000..13a7e18 --- /dev/null +++ b/education/education/doctype/program/program.js @@ -0,0 +1,8 @@ +// Copyright (c) 2015, InfluxERP +// For license information, please see license.txt + +cur_frm.add_fetch('fee_structure', 'total_amount', 'amount'); + +frappe.ui.form.on("Program", "refresh", function(frm) { + +}); diff --git a/education/education/doctype/program/program.json b/education/education/doctype/program/program.json new file mode 100644 index 0000000..2dfe50b --- /dev/null +++ b/education/education/doctype/program/program.json @@ -0,0 +1,177 @@ +{ + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:program_name", + "creation": "2015-09-07 12:54:03.609282", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "program_name", + "department", + "column_break_3", + "program_abbreviation", + "section_break_courses", + "courses", + "section_break_5", + "is_published", + "allow_self_enroll", + "is_featured", + "column_break_11", + "intro_video", + "hero_image", + "description" + ], + "fields": [ + { + "fieldname": "program_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Program Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "department", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Department", + "options": "Department" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "program_abbreviation", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Program Abbreviation" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Portal Settings" + }, + { + "fieldname": "courses", + "fieldtype": "Table", + "label": "Courses", + "options": "Program Course" + }, + { + "depends_on": "is_published", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "depends_on": "is_published", + "fieldname": "intro_video", + "fieldtype": "Data", + "label": "Intro Video" + }, + { + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Hero Image" + }, + { + "default": "0", + "fieldname": "is_published", + "fieldtype": "Check", + "label": "Is Published" + }, + { + "default": "0", + "depends_on": "eval: doc.is_published == 1", + "fieldname": "is_featured", + "fieldtype": "Check", + "label": "Is Featured" + }, + { + "default": "0", + "depends_on": "eval: doc.is_published == 1", + "fieldname": "allow_self_enroll", + "fieldtype": "Check", + "label": "Allow Self Enroll" + }, + { + "fieldname": "section_break_courses", + "fieldtype": "Section Break", + "label": "Courses" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + } + ], + "image_field": "hero_image", + "modified": "2019-06-12 12:31:14.999346", + "modified_by": "Administrator", + "module": "Education", + "name": "Program", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Student", + "share": 1 + } + ], + "restrict_to_domain": "Education", + "search_fields": "program_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/program/program.py b/education/education/doctype/program/program.py new file mode 100644 index 0000000..5c12dc1 --- /dev/null +++ b/education/education/doctype/program/program.py @@ -0,0 +1,16 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class Program(Document): + def get_course_list(self): + program_course_list = self.courses + course_list = [ + frappe.get_doc("Course", program_course.course) + for program_course in program_course_list + ] + return course_list diff --git a/education/education/doctype/program/program_dashboard.py b/education/education/doctype/program/program_dashboard.py new file mode 100644 index 0000000..599a7cf --- /dev/null +++ b/education/education/doctype/program/program_dashboard.py @@ -0,0 +1,16 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "program", + "transactions": [ + { + "label": _("Admission and Enrollment"), + "items": ["Student Applicant", "Program Enrollment"], + }, + {"label": _("Student Activity"), "items": ["Student Group", "Student Log"]}, + {"label": _("Fee"), "items": ["Fees", "Fee Structure", "Fee Schedule"]}, + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + ], + } diff --git a/education/education/doctype/program/test_program.py b/education/education/doctype/program/test_program.py new file mode 100644 index 0000000..76deb61 --- /dev/null +++ b/education/education/doctype/program/test_program.py @@ -0,0 +1,103 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe + +from education.education.doctype.course.test_course import ( + make_course, make_course_and_linked_topic) +from education.education.doctype.topic.test_topic import \ + make_topic_and_linked_content + +test_data = { + "program_name": "_Test Program", + "description": "_Test Program", + "course": [ + { + "course_name": "_Test Course 1", + "topic": [ + { + "topic_name": "_Test Topic 1-1", + "content": [ + {"type": "Article", "name": "_Test Article 1-1"}, + {"type": "Article", "name": "_Test Article 1-2"}, + ], + }, + { + "topic_name": "_Test Topic 1-2", + "content": [ + {"type": "Article", "name": "_Test Article 1-3"}, + {"type": "Article", "name": "_Test Article 1-4"}, + ], + }, + ], + } + ], +} + + +class TestProgram(unittest.TestCase): + def setUp(self): + make_program_and_linked_courses( + "_Test Program 1", ["_Test Course 1", "_Test Course 2"] + ) + + def test_get_course_list(self): + program = frappe.get_doc("Program", "_Test Program 1") + course = program.get_course_list() + self.assertEqual(course[0].name, "_Test Course 1") + self.assertEqual(course[1].name, "_Test Course 2") + frappe.db.rollback() + + def tearDown(self): + for dt in ["Program", "Course", "Topic", "Article"]: + for entry in frappe.get_all(dt): + frappe.delete_doc(dt, entry.program) + + +def make_program(name): + program = frappe.get_doc( + { + "doctype": "Program", + "program_name": name, + "program_code": name, + "description": "_test description", + "is_published": True, + "is_featured": True, + } + ).insert() + return program.name + + +def make_program_and_linked_courses(program_name, course_name_list): + try: + program = frappe.get_doc("Program", program_name) + except frappe.DoesNotExistError: + make_program(program_name) + program = frappe.get_doc("Program", program_name) + course_list = [make_course(course_name) for course_name in course_name_list] + for course in course_list: + program.append("courses", {"course": course, "required": 1}) + program.save() + return program + + +def setup_program(): + topic_list = [course["topic"] for course in test_data["course"]] + for topic in topic_list[0]: + make_topic_and_linked_content(topic["topic_name"], topic["content"]) + + all_courses_list = [ + { + "course": course["course_name"], + "topic": [topic["topic_name"] for topic in course["topic"]], + } + for course in test_data["course"] + ] # returns [{'course': 'Applied Math', 'topic': ['Trignometry', 'Geometry']}] + for course in all_courses_list: + make_course_and_linked_topic(course["course"], course["topic"]) + + course_list = [course["course_name"] for course in test_data["course"]] + program = make_program_and_linked_courses(test_data["program_name"], course_list) + return program diff --git a/education/education/doctype/program/test_records.json b/education/education/doctype/program/test_records.json new file mode 100644 index 0000000..4013695 --- /dev/null +++ b/education/education/doctype/program/test_records.json @@ -0,0 +1,12 @@ +[ + { + "program_name": "_TP1", + "description": "Test Description", + "program_abbreviation": "TP1" + }, + { + "program_name": "_TP2", + "description": "Test Description", + "program_abbreviation": "TP2" + } +] \ No newline at end of file diff --git a/education/education/doctype/program_course/__init__.py b/education/education/doctype/program_course/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_course/program_course.json b/education/education/doctype/program_course/program_course.json new file mode 100644 index 0000000..dc6b3fc --- /dev/null +++ b/education/education/doctype/program_course/program_course.json @@ -0,0 +1,50 @@ +{ + "actions": [], + "creation": "2015-09-07 14:37:01.886859", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "course", + "course_name", + "required" + ], + "fields": [ + { + "fieldname": "course", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Course", + "options": "Course", + "reqd": 1 + }, + { + "fetch_from": "course.course_name", + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "read_only": 1 + }, + { + "default": "1", + "fieldname": "required", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory" + } + ], + "istable": 1, + "links": [], + "modified": "2020-09-15 18:14:22.816795", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Course", + "owner": "Administrator", + "permissions": [], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/program_course/program_course.py b/education/education/doctype/program_course/program_course.py new file mode 100644 index 0000000..2811ea8 --- /dev/null +++ b/education/education/doctype/program_course/program_course.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProgramCourse(Document): + pass diff --git a/education/education/doctype/program_enrollment/__init__.py b/education/education/doctype/program_enrollment/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_enrollment/program_enrollment.js b/education/education/doctype/program_enrollment/program_enrollment.js new file mode 100644 index 0000000..adbea73 --- /dev/null +++ b/education/education/doctype/program_enrollment/program_enrollment.js @@ -0,0 +1,104 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + + +frappe.ui.form.on('Program Enrollment', { + setup: function(frm) { + frm.add_fetch('fee_structure', 'total_amount', 'amount'); + }, + + onload: function(frm) { + frm.set_query('academic_term', function() { + return { + 'filters':{ + 'academic_year': frm.doc.academic_year + } + }; + }); + + frm.set_query('academic_term', 'fees', function() { + return { + 'filters':{ + 'academic_year': frm.doc.academic_year + } + }; + }); + + frm.fields_dict['fees'].grid.get_field('fee_structure').get_query = function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + filters: {'academic_term': d.academic_term} + } + }; + + if (frm.doc.program) { + frm.set_query('course', 'courses', function() { + return { + query: 'education.education.doctype.program_enrollment.program_enrollment.get_program_courses', + filters: { + 'program': frm.doc.program + } + } + }); + } + + frm.set_query('student', function() { + return{ + query: 'education.education.doctype.program_enrollment.program_enrollment.get_students', + filters: { + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term + } + } + }); + }, + + program: function(frm) { + frm.events.get_courses(frm); + if (frm.doc.program) { + frappe.call({ + method: 'education.education.api.get_fee_schedule', + args: { + 'program': frm.doc.program, + 'student_category': frm.doc.student_category + }, + callback: function(r) { + if (r.message) { + frm.set_value('fees' ,r.message); + frm.events.get_courses(frm); + } + } + }); + } + }, + + student_category: function() { + frappe.ui.form.trigger('Program Enrollment', 'program'); + }, + + get_courses: function(frm) { + frm.set_value('courses',[]); + frappe.call({ + method: 'get_courses', + doc:frm.doc, + callback: function(r) { + if (r.message) { + frm.set_value('courses', r.message); + } + } + }) + } +}); + +frappe.ui.form.on('Program Enrollment Course', { + courses_add: function(frm){ + frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc) { + var course_list = []; + if(!doc.__islocal) course_list.push(doc.name); + $.each(doc.courses, function(_idx, val) { + if (val.course) course_list.push(val.course); + }); + return { filters: [['Course', 'name', 'not in', course_list]] }; + }; + } +}); diff --git a/education/education/doctype/program_enrollment/program_enrollment.json b/education/education/doctype/program_enrollment/program_enrollment.json new file mode 100644 index 0000000..4a00fd0 --- /dev/null +++ b/education/education/doctype/program_enrollment/program_enrollment.json @@ -0,0 +1,218 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "EDU-ENR-.YYYY.-.#####", + "creation": "2015-12-02 12:58:32.916080", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "student", + "student_name", + "student_category", + "student_batch_name", + "school_house", + "column_break_4", + "program", + "academic_year", + "academic_term", + "enrollment_date", + "boarding_student", + "enrolled_courses", + "courses", + "transportation", + "mode_of_transportation", + "column_break_13", + "vehicle_no", + "section_break_7", + "fees", + "amended_from", + "image" + ], + "fields": [ + { + "fieldname": "student", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Student", + "options": "Student", + "reqd": 1 + }, + { + "fetch_from": "student.title", + "fieldname": "student_name", + "fieldtype": "Read Only", + "in_global_search": 1, + "label": "Student Name", + "read_only": 1 + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category" + }, + { + "allow_on_submit": 1, + "fieldname": "student_batch_name", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Student Batch", + "options": "Student Batch Name" + }, + { + "allow_on_submit": 1, + "fieldname": "school_house", + "fieldtype": "Link", + "label": "School House", + "options": "School House" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "program", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Program", + "options": "Program", + "reqd": 1 + }, + { + "fieldname": "academic_year", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Academic Year", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term" + }, + { + "default": "Today", + "fieldname": "enrollment_date", + "fieldtype": "Date", + "label": "Enrollment Date", + "reqd": 1 + }, + { + "default": "0", + "description": "Check this if the Student is residing at the Institute's Hostel.", + "fieldname": "boarding_student", + "fieldtype": "Check", + "label": "Boarding Student" + }, + { + "collapsible": 1, + "collapsible_depends_on": "vehicle_no", + "fieldname": "transportation", + "fieldtype": "Section Break", + "label": "Transportation" + }, + { + "allow_on_submit": 1, + "fieldname": "mode_of_transportation", + "fieldtype": "Select", + "label": "Mode of Transportation", + "options": "\nWalking\nInstitute's Bus\nPublic Transport\nSelf-Driving Vehicle\nPick/Drop by Guardian" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fieldname": "vehicle_no", + "fieldtype": "Data", + "label": "Vehicle/Bus Number" + }, + { + "fieldname": "enrolled_courses", + "fieldtype": "Section Break", + "label": "Enrolled courses" + }, + { + "allow_on_submit": 1, + "fieldname": "courses", + "fieldtype": "Table", + "label": "Courses", + "options": "Program Enrollment Course" + }, + { + "collapsible": 1, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Fees" + }, + { + "fieldname": "fees", + "fieldtype": "Table", + "label": "Fees", + "options": "Program Fee" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Program Enrollment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image" + } + ], + "image_field": "image", + "is_submittable": 1, + "links": [], + "modified": "2020-09-15 18:12:11.988565", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment", + "owner": "Administrator", + "permissions": [ + { + "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 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "student_name" +} \ No newline at end of file diff --git a/education/education/doctype/program_enrollment/program_enrollment.py b/education/education/doctype/program_enrollment/program_enrollment.py new file mode 100644 index 0000000..e8c63f1 --- /dev/null +++ b/education/education/doctype/program_enrollment/program_enrollment.py @@ -0,0 +1,227 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _, msgprint +from frappe.desk.reportview import get_match_cond +from frappe.model.document import Document +from frappe.query_builder.functions import Min +from frappe.utils import comma_and, get_link_to_form, getdate + + +class ProgramEnrollment(Document): + def validate(self): + self.validate_duplication() + self.validate_academic_year() + if self.academic_term: + self.validate_academic_term() + if not self.student_name: + self.student_name = frappe.db.get_value("Student", self.student, "title") + if not self.courses: + self.extend("courses", self.get_courses()) + + def on_submit(self): + self.update_student_joining_date() + self.make_fee_records() + self.create_course_enrollments() + + def validate_academic_year(self): + start_date, end_date = frappe.db.get_value( + "Academic Year", self.academic_year, ["year_start_date", "year_end_date"] + ) + if self.enrollment_date: + if start_date and getdate(self.enrollment_date) < getdate(start_date): + frappe.throw( + _( + "Enrollment Date cannot be before the Start Date of the Academic Year {0}" + ).format(get_link_to_form("Academic Year", self.academic_year)) + ) + + if end_date and getdate(self.enrollment_date) > getdate(end_date): + frappe.throw( + _("Enrollment Date cannot be after the End Date of the Academic Term {0}").format( + get_link_to_form("Academic Year", self.academic_year) + ) + ) + + def validate_academic_term(self): + start_date, end_date = frappe.db.get_value( + "Academic Term", self.academic_term, ["term_start_date", "term_end_date"] + ) + if self.enrollment_date: + if start_date and getdate(self.enrollment_date) < getdate(start_date): + frappe.throw( + _( + "Enrollment Date cannot be before the Start Date of the Academic Term {0}" + ).format(get_link_to_form("Academic Term", self.academic_term)) + ) + + if end_date and getdate(self.enrollment_date) > getdate(end_date): + frappe.throw( + _("Enrollment Date cannot be after the End Date of the Academic Term {0}").format( + get_link_to_form("Academic Term", self.academic_term) + ) + ) + + def validate_duplication(self): + enrollment = frappe.get_all( + "Program Enrollment", + filters={ + "student": self.student, + "program": self.program, + "academic_year": self.academic_year, + "academic_term": self.academic_term, + "docstatus": ("<", 2), + "name": ("!=", self.name), + }, + ) + if enrollment: + frappe.throw(_("Student is already enrolled.")) + + def update_student_joining_date(self): + table = frappe.qb.DocType("Program Enrollment") + date = ( + frappe.qb.from_(table) + .select(Min(table.enrollment_date).as_("enrollment_date")) + .where(table.student == self.student) + ).run(as_dict=True) + + if date: + frappe.db.set_value("Student", self.student, "joining_date", date[0].enrollment_date) + + def make_fee_records(self): + from education.education.api import get_fee_components + + fee_list = [] + for d in self.fees: + fee_components = get_fee_components(d.fee_structure) + if fee_components: + fees = frappe.new_doc("Fees") + fees.update( + { + "student": self.student, + "academic_year": self.academic_year, + "academic_term": d.academic_term, + "fee_structure": d.fee_structure, + "program": self.program, + "due_date": d.due_date, + "student_name": self.student_name, + "program_enrollment": self.name, + "components": fee_components, + } + ) + + fees.save() + fees.submit() + fee_list.append(fees.name) + if fee_list: + fee_list = [ + """%s""" % (fee, fee) + for fee in fee_list + ] + msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list))) + + @frappe.whitelist() + def get_courses(self): + return frappe.db.sql( + """select course from `tabProgram Course` where parent = %s and required = 1""", + (self.program), + as_dict=1, + ) + + def create_course_enrollments(self): + student = frappe.get_doc("Student", self.student) + course_list = [course.course for course in self.courses] + for course_name in course_list: + student.enroll_in_course( + course_name=course_name, + program_enrollment=self.name, + enrollment_date=self.enrollment_date, + ) + + def get_all_course_enrollments(self): + course_enrollment_names = frappe.get_list( + "Course Enrollment", filters={"program_enrollment": self.name} + ) + return [ + frappe.get_doc("Course Enrollment", course_enrollment.name) + for course_enrollment in course_enrollment_names + ] + + def get_quiz_progress(self): + student = frappe.get_doc("Student", self.student) + quiz_progress = frappe._dict() + progress_list = [] + for course_enrollment in self.get_all_course_enrollments(): + course_progress = course_enrollment.get_progress(student) + for progress_item in course_progress: + if progress_item["content_type"] == "Quiz": + progress_item["course"] = course_enrollment.course + progress_list.append(progress_item) + if not progress_list: + return None + quiz_progress.quiz_attempt = progress_list + quiz_progress.name = self.program + quiz_progress.program = self.program + return quiz_progress + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_program_courses(doctype, txt, searchfield, start, page_len, filters): + if not filters.get("program"): + frappe.msgprint(_("Please select a Program first.")) + return [] + + return frappe.db.sql( + """select course, course_name from `tabProgram Course` + where parent = %(program)s and course like %(txt)s {match_cond} + order by + if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999), + idx desc, + `tabProgram Course`.course asc + limit {start}, {page_len}""".format( + match_cond=get_match_cond(doctype), start=start, page_len=page_len + ), + { + "txt": "%{0}%".format(txt), + "_txt": txt.replace("%", ""), + "program": filters["program"], + }, + ) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_students(doctype, txt, searchfield, start, page_len, filters): + if not filters.get("academic_term"): + filters["academic_term"] = frappe.defaults.get_defaults().academic_term + + if not filters.get("academic_year"): + filters["academic_year"] = frappe.defaults.get_defaults().academic_year + + enrolled_students = frappe.get_list( + "Program Enrollment", + filters={ + "academic_term": filters.get("academic_term"), + "academic_year": filters.get("academic_year"), + }, + fields=["student"], + ) + + students = [d.student for d in enrolled_students] if enrolled_students else [""] + + return frappe.db.sql( + """select + name, title from tabStudent + where + name not in (%s) + and + `%s` LIKE %s + order by + idx desc, name + limit %s, %s""" + % (", ".join(["%s"] * len(students)), searchfield, "%s", "%s", "%s"), + tuple(students + ["%%%s%%" % txt, start, page_len]), + ) diff --git a/education/education/doctype/program_enrollment/program_enrollment_dashboard.py b/education/education/doctype/program_enrollment/program_enrollment_dashboard.py new file mode 100644 index 0000000..d6bf80e --- /dev/null +++ b/education/education/doctype/program_enrollment/program_enrollment_dashboard.py @@ -0,0 +1,13 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "program_enrollment", + "transactions": [ + {"label": _("Course and Fee"), "items": ["Course Enrollment", "Fees"]} + ], + "reports": [ + {"label": _("Report"), "items": ["Student and Guardian Contact Details"]} + ], + } diff --git a/education/education/doctype/program_enrollment/test_program_enrollment.py b/education/education/doctype/program_enrollment/test_program_enrollment.py new file mode 100644 index 0000000..fe1c3d3 --- /dev/null +++ b/education/education/doctype/program_enrollment/test_program_enrollment.py @@ -0,0 +1,42 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe + +from education.education.doctype.program.test_program import \ + make_program_and_linked_courses +from education.education.doctype.student.test_student import (create_student, + get_student) + + +class TestProgramEnrollment(unittest.TestCase): + def setUp(self): + create_student( + { + "first_name": "_Test Name", + "last_name": "_Test Last Name", + "email": "_test_student@example.com", + } + ) + make_program_and_linked_courses( + "_Test Program 1", ["_Test Course 1", "_Test Course 2"] + ) + + def test_create_course_enrollments(self): + student = get_student("_test_student@example.com") + enrollment = student.enroll_in_program("_Test Program 1") + course_enrollments = student.get_all_course_enrollments() + self.assertTrue("_Test Course 1" in course_enrollments.keys()) + self.assertTrue("_Test Course 2" in course_enrollments.keys()) + frappe.db.rollback() + + def tearDown(self): + for entry in frappe.db.get_all("Course Enrollment"): + frappe.delete_doc("Course Enrollment", entry.name) + + for entry in frappe.db.get_all("Program Enrollment"): + doc = frappe.get_doc("Program Enrollment", entry.name) + doc.cancel() + doc.delete() diff --git a/education/education/doctype/program_enrollment_course/__init__.py b/education/education/doctype/program_enrollment_course/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_enrollment_course/program_enrollment_course.json b/education/education/doctype/program_enrollment_course/program_enrollment_course.json new file mode 100644 index 0000000..502a401 --- /dev/null +++ b/education/education/doctype/program_enrollment_course/program_enrollment_course.json @@ -0,0 +1,107 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-04-10 19:28:19.616308", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "course", + "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", + "length": 0, + "no_copy": 0, + "options": "Course", + "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, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "course.course_name", + "fieldname": "course_name", + "fieldtype": "Data", + "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 Name", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-05-16 22:42:39.136276", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Course", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/program_enrollment_course/program_enrollment_course.py b/education/education/doctype/program_enrollment_course/program_enrollment_course.py new file mode 100644 index 0000000..397328d --- /dev/null +++ b/education/education/doctype/program_enrollment_course/program_enrollment_course.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProgramEnrollmentCourse(Document): + pass diff --git a/education/education/doctype/program_enrollment_fee/__init__.py b/education/education/doctype/program_enrollment_fee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_enrollment_fee/program_enrollment_fee.json b/education/education/doctype/program_enrollment_fee/program_enrollment_fee.json new file mode 100644 index 0000000..0af2815 --- /dev/null +++ b/education/education/doctype/program_enrollment_fee/program_enrollment_fee.json @@ -0,0 +1,192 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-05-19 05:52:56.322125", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fee_structure", + "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": "Fee Structure", + "length": 0, + "no_copy": 0, + "options": "Fee Structure", + "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, + "unique": 0 + }, + { + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "due_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": "Due 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": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:11:07.516632", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Fee", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/program_enrollment_fee/program_enrollment_fee.py b/education/education/doctype/program_enrollment_fee/program_enrollment_fee.py new file mode 100644 index 0000000..0164459 --- /dev/null +++ b/education/education/doctype/program_enrollment_fee/program_enrollment_fee.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProgramEnrollmentFee(Document): + pass diff --git a/education/education/doctype/program_enrollment_tool/__init__.py b/education/education/doctype/program_enrollment_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_enrollment_tool/program_enrollment_tool.js b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.js new file mode 100644 index 0000000..3d0be8c --- /dev/null +++ b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.js @@ -0,0 +1,51 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on("Program Enrollment Tool", { + setup: function(frm) { + frm.add_fetch("student", "title", "student_name"); + frm.add_fetch("student_applicant", "title", "student_name"); + if(frm.doc.__onload && frm.doc.__onload.academic_term_reqd) { + frm.toggle_reqd("academic_term", true); + } + }, + + "refresh": function(frm) { + frm.disable_save(); + frm.fields_dict.enroll_students.$input.addClass(' btn btn-primary'); + frappe.realtime.on("program_enrollment_tool", function(data) { + frappe.hide_msgprint(true); + frappe.show_progress(__("Enrolling students"), data.progress[0], data.progress[1]); + }); + }, + + get_students_from: function(frm) { + if (frm.doc.get_students_from == "Student Applicant") { + frm.dashboard.add_comment(__('Only the Student Applicant with the status "Approved" will be selected in the table below.')); + } + }, + + "get_students": function(frm) { + frm.set_value("students",[]); + frappe.call({ + method: "get_students", + doc:frm.doc, + callback: function(r) { + if(r.message) { + frm.set_value("students", r.message); + } + } + }); + }, + + "enroll_students": function(frm) { + frappe.call({ + method: "enroll_students", + doc:frm.doc, + callback: function(r) { + frm.set_value("students", []); + frappe.hide_msgprint(true); + } + }); + } +}); diff --git a/education/education/doctype/program_enrollment_tool/program_enrollment_tool.json b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.json new file mode 100644 index 0000000..35ad98d --- /dev/null +++ b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.json @@ -0,0 +1,553 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-06-10 03:01:05.178956", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "get_students_from", + "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": 0, + "label": "Get Students From", + "length": 0, + "no_copy": 0, + "options": "\nStudent Applicant\nProgram Enrollment", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "program", + "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": "Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.get_students_from==\"Program Enrollment\"", + "fieldname": "student_batch", + "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": "Student Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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, + "unique": 0 + }, + { + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year", + "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": "Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "get_students", + "fieldtype": "Button", + "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": "Get Students", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "students", + "fieldtype": "Table", + "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": "Students", + "length": 0, + "no_copy": 0, + "options": "Program Enrollment Tool 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": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "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, + "label": "Enrollment Details", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.get_students_from==\"Program Enrollment\"", + "fieldname": "new_program", + "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": "New Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "new_student_batch", + "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": "New Student Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enroll_students", + "fieldtype": "Button", + "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": "Enroll Students", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_12", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.get_students_from==\"Program Enrollment\"", + "fieldname": "new_academic_year", + "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": "New Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.get_students_from==\"Program Enrollment\"", + "fieldname": "new_academic_term", + "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": "New Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 1, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2018-07-26 04:44:13.232146", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Tool", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Education Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "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", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.py new file mode 100644 index 0000000..85a75f8 --- /dev/null +++ b/education/education/doctype/program_enrollment_tool/program_enrollment_tool.py @@ -0,0 +1,99 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint + +from education.education.api import enroll_student + + +class ProgramEnrollmentTool(Document): + def onload(self): + academic_term_reqd = cint( + frappe.db.get_single_value("Education Settings", "academic_term_reqd") + ) + self.set_onload("academic_term_reqd", academic_term_reqd) + + @frappe.whitelist() + def get_students(self): + students = [] + if not self.get_students_from: + frappe.throw(_("Mandatory field - Get Students From")) + elif not self.program: + frappe.throw(_("Mandatory field - Program")) + elif not self.academic_year: + frappe.throw(_("Mandatory field - Academic Year")) + else: + condition = "and academic_term=%(academic_term)s" if self.academic_term else " " + if self.get_students_from == "Student Applicant": + students = frappe.db.sql( + """select name as student_applicant, title as student_name from `tabStudent Applicant` + where application_status="Approved" and program=%(program)s and academic_year=%(academic_year)s {0}""".format( + condition + ), + self.as_dict(), + as_dict=1, + ) + elif self.get_students_from == "Program Enrollment": + condition2 = ( + "and student_batch_name=%(student_batch)s" if self.student_batch else " " + ) + students = frappe.db.sql( + """select student, student_name, student_batch_name, student_category from `tabProgram Enrollment` + where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2""".format( + condition, condition2 + ), + self.as_dict(), + as_dict=1, + ) + + student_list = [d.student for d in students] + if student_list: + inactive_students = frappe.db.sql( + """ + select name as student, title as student_name from `tabStudent` where name in (%s) and enabled = 0""" + % ", ".join(["%s"] * len(student_list)), + tuple(student_list), + as_dict=1, + ) + + for student in students: + if student.student in [d.student for d in inactive_students]: + students.remove(student) + + if students: + return students + else: + frappe.throw(_("No students Found")) + + @frappe.whitelist() + def enroll_students(self): + total = len(self.students) + for i, stud in enumerate(self.students): + frappe.publish_realtime( + "program_enrollment_tool", dict(progress=[i + 1, total]), user=frappe.session.user + ) + if stud.student: + prog_enrollment = frappe.new_doc("Program Enrollment") + prog_enrollment.student = stud.student + prog_enrollment.student_name = stud.student_name + prog_enrollment.student_category = stud.student_category + prog_enrollment.program = self.new_program + prog_enrollment.academic_year = self.new_academic_year + prog_enrollment.academic_term = self.new_academic_term + prog_enrollment.student_batch_name = ( + stud.student_batch_name if stud.student_batch_name else self.new_student_batch + ) + prog_enrollment.save() + elif stud.student_applicant: + prog_enrollment = enroll_student(stud.student_applicant) + prog_enrollment.academic_year = self.academic_year + prog_enrollment.academic_term = self.academic_term + prog_enrollment.student_batch_name = ( + stud.student_batch_name if stud.student_batch_name else self.new_student_batch + ) + prog_enrollment.save() + frappe.msgprint(_("{0} Students have been enrolled").format(total)) diff --git a/education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py b/education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py new file mode 100644 index 0000000..0ac1ec4 --- /dev/null +++ b/education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestProgramEnrollmentTool(unittest.TestCase): + pass diff --git a/education/education/doctype/program_enrollment_tool_student/__init__.py b/education/education/doctype/program_enrollment_tool_student/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json new file mode 100644 index 0000000..1d74973 --- /dev/null +++ b/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json @@ -0,0 +1,68 @@ +{ + "actions": [], + "creation": "2016-06-10 03:29:02.539914", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "student_applicant", + "student", + "student_name", + "column_break_3", + "student_batch_name", + "student_category" + ], + "fields": [ + { + "fieldname": "student_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Applicant", + "options": "Student Applicant" + }, + { + "fieldname": "student", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student", + "options": "Student" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "student_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Student Name", + "read_only": 1 + }, + { + "fieldname": "student_batch_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Batch Name", + "options": "Student Batch Name" + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2021-07-29 18:19:54.471594", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Tool Student", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.py b/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.py new file mode 100644 index 0000000..1558363 --- /dev/null +++ b/education/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProgramEnrollmentToolStudent(Document): + pass diff --git a/education/education/doctype/program_fee/__init__.py b/education/education/doctype/program_fee/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/program_fee/program_fee.json b/education/education/doctype/program_fee/program_fee.json new file mode 100644 index 0000000..d45e4bd --- /dev/null +++ b/education/education/doctype/program_fee/program_fee.json @@ -0,0 +1,224 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-05-19 05:52:56.322125", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fee_structure", + "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": "Fee Structure", + "length": 0, + "no_copy": 0, + "options": "Fee Structure", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_category", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Student Category", + "length": 0, + "no_copy": 0, + "options": "Student Category", + "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, + "unique": 0 + }, + { + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "due_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": "Due 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": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-11-10 19:07:10.426335", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Fee", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/program_fee/program_fee.py b/education/education/doctype/program_fee/program_fee.py new file mode 100644 index 0000000..85f9118 --- /dev/null +++ b/education/education/doctype/program_fee/program_fee.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class ProgramFee(Document): + pass diff --git a/education/education/doctype/question/__init__.py b/education/education/doctype/question/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/question/question.js b/education/education/doctype/question/question.js new file mode 100644 index 0000000..bda3a35 --- /dev/null +++ b/education/education/doctype/question/question.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Question', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/question/question.json b/education/education/doctype/question/question.json new file mode 100644 index 0000000..e396760 --- /dev/null +++ b/education/education/doctype/question/question.json @@ -0,0 +1,80 @@ +{ + "allow_import": 1, + "autoname": "format:QUESTION-{#####}", + "creation": "2018-10-01 15:58:00.696815", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "options", + "question_type" + ], + "fields": [ + { + "fieldname": "question", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Question", + "reqd": 1 + }, + { + "fieldname": "options", + "fieldtype": "Table", + "label": "Options", + "options": "Options", + "reqd": 1 + }, + { + "fieldname": "question_type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "\nSingle Correct Answer\nMultiple Correct Answer", + "read_only": 1 + } + ], + "modified": "2020-09-24 18:39:21.880974", + "modified_by": "Administrator", + "module": "Education", + "name": "Question", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} diff --git a/education/education/doctype/question/question.py b/education/education/doctype/question/question.py new file mode 100644 index 0000000..6813fa7 --- /dev/null +++ b/education/education/doctype/question/question.py @@ -0,0 +1,45 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class Question(Document): + def validate(self): + self.check_at_least_one_option() + self.check_minimum_one_correct_answer() + self.set_question_type() + + def check_at_least_one_option(self): + if len(self.options) <= 1: + frappe.throw(_("A question must have more than one options")) + else: + pass + + def check_minimum_one_correct_answer(self): + correct_options = [option.is_correct for option in self.options] + if bool(sum(correct_options)): + pass + else: + frappe.throw(_("A qustion must have at least one correct options")) + + def set_question_type(self): + correct_options = [option for option in self.options if option.is_correct] + if len(correct_options) > 1: + self.question_type = "Multiple Correct Answer" + else: + self.question_type = "Single Correct Answer" + + def get_answer(self): + options = self.options + answers = [item.name for item in options if item.is_correct == True] + if len(answers) == 0: + frappe.throw(_("No correct answer is set for {0}").format(self.name)) + return None + elif len(answers) == 1: + return answers[0] + else: + return answers diff --git a/education/education/doctype/question/test_question.py b/education/education/doctype/question/test_question.py new file mode 100644 index 0000000..698f763 --- /dev/null +++ b/education/education/doctype/question/test_question.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestQuestion(unittest.TestCase): + pass diff --git a/education/education/doctype/quiz/__init__.py b/education/education/doctype/quiz/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/quiz/quiz.js b/education/education/doctype/quiz/quiz.js new file mode 100644 index 0000000..1331a1f --- /dev/null +++ b/education/education/doctype/quiz/quiz.js @@ -0,0 +1,71 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Quiz', { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__('Add to Topics'), function() { + frm.trigger('add_quiz_to_topics'); + }, __('Action')); + } + }, + + validate: function(frm){ + frm.events.check_duplicate_question(frm.doc.question); + }, + + check_duplicate_question: function(questions_data){ + var questions = []; + questions_data.forEach(function(q){ + questions.push(q.question_link); + }); + var questions_set = new Set(questions); + if (questions.length != questions_set.size) { + frappe.throw(__('The question cannot be duplicate')); + } + }, + + add_quiz_to_topics: function(frm) { + get_topics_without_quiz(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'topics', + label: __('Topics'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'education.education.doctype.topic.topic.add_content_to_topics', + args: { + 'content_type': 'Quiz', + 'content': frm.doc.name, + 'topics': data.topics, + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Quiz to Topics') + }); + }, __('Add Quiz to Topics'), __('Add')); + } else { + frappe.msgprint(__('This quiz is already added to the existing topics')); + } + }); + } +}); + +let get_topics_without_quiz = function(quiz) { + return frappe.call({ + type: 'GET', + method: 'education.education.doctype.quiz.quiz.get_topics_without_quiz', + args: {'quiz': quiz} + }); +}; diff --git a/education/education/doctype/quiz/quiz.json b/education/education/doctype/quiz/quiz.json new file mode 100644 index 0000000..16d7d7e --- /dev/null +++ b/education/education/doctype/quiz/quiz.json @@ -0,0 +1,128 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:title", + "creation": "2018-10-17 05:52:50.149904", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "question", + "quiz_configuration_section", + "passing_score", + "max_attempts", + "grading_basis", + "column_break_7", + "is_time_bound", + "duration" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "question", + "fieldtype": "Table", + "label": "Question", + "options": "Quiz Question", + "reqd": 1 + }, + { + "fieldname": "quiz_configuration_section", + "fieldtype": "Section Break", + "label": "Quiz Configuration" + }, + { + "default": "75", + "description": "Score out of 100", + "fieldname": "passing_score", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Passing Score", + "reqd": 1 + }, + { + "default": "1", + "description": "Enter 0 to waive limit", + "fieldname": "max_attempts", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Max Attempts", + "reqd": 1 + }, + { + "default": "Latest Highest Score", + "fieldname": "grading_basis", + "fieldtype": "Select", + "label": "Grading Basis", + "options": "Latest Highest Score\nLatest Attempt" + }, + { + "default": "0", + "fieldname": "is_time_bound", + "fieldtype": "Check", + "label": "Is Time-Bound" + }, + { + "depends_on": "is_time_bound", + "fieldname": "duration", + "fieldtype": "Duration", + "label": "Duration" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + } + ], + "links": [], + "modified": "2020-12-24 15:41:35.043262", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/quiz/quiz.py b/education/education/doctype/quiz/quiz.py new file mode 100644 index 0000000..b945e95 --- /dev/null +++ b/education/education/doctype/quiz/quiz.py @@ -0,0 +1,79 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class Quiz(Document): + def validate(self): + if self.passing_score > 100: + frappe.throw(_("Passing Score value should be between 0 and 100")) + + def allowed_attempt(self, enrollment, quiz_name): + if self.max_attempts == 0: + return True + + try: + if ( + len( + frappe.get_all("Quiz Activity", {"enrollment": enrollment.name, "quiz": quiz_name}) + ) + >= self.max_attempts + ): + frappe.msgprint(_("Maximum attempts for this quiz reached!")) + return False + else: + return True + except Exception as e: + return False + + def evaluate(self, response_dict, quiz_name): + questions = [ + frappe.get_doc("Question", question.question_link) for question in self.question + ] + answers = {q.name: q.get_answer() for q in questions} + result = {} + for key in answers: + try: + if isinstance(response_dict[key], list): + is_correct = compare_list_elementwise(response_dict[key], answers[key]) + else: + is_correct = response_dict[key] == answers[key] + except Exception as e: + is_correct = False + result[key] = is_correct + score = (sum(result.values()) * 100) / len(answers) + if score >= self.passing_score: + status = "Pass" + else: + status = "Fail" + return result, score, status + + def get_questions(self): + return [ + frappe.get_doc("Question", question.question_link) for question in self.question + ] + + +def compare_list_elementwise(*args): + try: + if all(len(args[0]) == len(_arg) for _arg in args[1:]): + return all(all([element in (item) for element in args[0]]) for item in args[1:]) + else: + return False + except TypeError: + frappe.throw(_("Compare List function takes on list arguments")) + + +@frappe.whitelist() +def get_topics_without_quiz(quiz): + data = [] + for entry in frappe.db.get_all("Topic"): + topic = frappe.get_doc("Topic", entry.name) + topic_contents = [tc.content for tc in topic.topic_content] + if not topic_contents or quiz not in topic_contents: + data.append(topic.name) + return data diff --git a/education/education/doctype/quiz/test_quiz.py b/education/education/doctype/quiz/test_quiz.py new file mode 100644 index 0000000..48de988 --- /dev/null +++ b/education/education/doctype/quiz/test_quiz.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestQuiz(unittest.TestCase): + pass diff --git a/education/education/doctype/quiz_activity/__init__.py b/education/education/doctype/quiz_activity/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/quiz_activity/quiz_activity.js b/education/education/doctype/quiz_activity/quiz_activity.js new file mode 100644 index 0000000..da07113 --- /dev/null +++ b/education/education/doctype/quiz_activity/quiz_activity.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Quiz Activity', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/quiz_activity/quiz_activity.json b/education/education/doctype/quiz_activity/quiz_activity.json new file mode 100644 index 0000000..742c887 --- /dev/null +++ b/education/education/doctype/quiz_activity/quiz_activity.json @@ -0,0 +1,163 @@ +{ + "actions": [], + "autoname": "format:EDU-QA-{YYYY}-{#####}", + "beta": 1, + "creation": "2018-10-15 15:48:40.482821", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enrollment", + "student", + "column_break_3", + "course", + "section_break_5", + "quiz", + "column_break_7", + "status", + "section_break_9", + "result", + "section_break_11", + "activity_date", + "score", + "column_break_14", + "time_taken" + ], + "fields": [ + { + "fieldname": "enrollment", + "fieldtype": "Link", + "label": "Enrollment", + "options": "Course Enrollment", + "set_only_once": 1 + }, + { + "fetch_from": "enrollment.student", + "fieldname": "student", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student", + "options": "Student", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "enrollment.course", + "fieldname": "course", + "fieldtype": "Link", + "label": "Course", + "options": "Course", + "read_only": 1, + "set_only_once": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "quiz", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Quiz", + "options": "Quiz", + "set_only_once": 1 + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "\nPass\nFail", + "read_only": 1 + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "result", + "fieldtype": "Table", + "label": "Result", + "options": "Quiz Result", + "set_only_once": 1 + }, + { + "fieldname": "activity_date", + "fieldtype": "Data", + "label": "Activity Date", + "set_only_once": 1 + }, + { + "fieldname": "score", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Score", + "set_only_once": 1 + }, + { + "fieldname": "time_taken", + "fieldtype": "Duration", + "label": "Time Taken", + "set_only_once": 1 + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_14", + "fieldtype": "Column Break" + } + ], + "links": [], + "modified": "2020-12-24 15:41:20.085380", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Activity", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/quiz_activity/quiz_activity.py b/education/education/doctype/quiz_activity/quiz_activity.py new file mode 100644 index 0000000..aaf177e --- /dev/null +++ b/education/education/doctype/quiz_activity/quiz_activity.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class QuizActivity(Document): + pass diff --git a/education/education/doctype/quiz_activity/test_quiz_activity.py b/education/education/doctype/quiz_activity/test_quiz_activity.py new file mode 100644 index 0000000..53087a8 --- /dev/null +++ b/education/education/doctype/quiz_activity/test_quiz_activity.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestQuizActivity(unittest.TestCase): + pass diff --git a/education/education/doctype/quiz_question/__init__.py b/education/education/doctype/quiz_question/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/quiz_question/quiz_question.json b/education/education/doctype/quiz_question/quiz_question.json new file mode 100644 index 0000000..aab07a3 --- /dev/null +++ b/education/education/doctype/quiz_question/quiz_question.json @@ -0,0 +1,40 @@ +{ + "allow_rename": 1, + "creation": "2018-10-17 06:13:00.098883", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question_link", + "question" + ], + "fields": [ + { + "fieldname": "question_link", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Question Link", + "options": "Question", + "reqd": 1 + }, + { + "fetch_from": "question_link.question", + "fieldname": "question", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Question", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2020-09-24 12:24:02.312577", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Question", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} diff --git a/education/education/doctype/quiz_question/quiz_question.py b/education/education/doctype/quiz_question/quiz_question.py new file mode 100644 index 0000000..e358dce --- /dev/null +++ b/education/education/doctype/quiz_question/quiz_question.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class QuizQuestion(Document): + pass diff --git a/education/education/doctype/quiz_result/__init__.py b/education/education/doctype/quiz_result/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/quiz_result/quiz_result.js b/education/education/doctype/quiz_result/quiz_result.js new file mode 100644 index 0000000..5f58e4b --- /dev/null +++ b/education/education/doctype/quiz_result/quiz_result.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Quiz Result', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/quiz_result/quiz_result.json b/education/education/doctype/quiz_result/quiz_result.json new file mode 100644 index 0000000..67c7e2d --- /dev/null +++ b/education/education/doctype/quiz_result/quiz_result.json @@ -0,0 +1,52 @@ +{ + "creation": "2018-10-15 15:52:25.766374", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "question", + "selected_option", + "quiz_result" + ], + "fields": [ + { + "fieldname": "question", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Question", + "options": "Question", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "selected_option", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Selected Option", + "read_only": 1, + "set_only_once": 1 + }, + { + "fieldname": "quiz_result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCorrect\nWrong", + "read_only": 1, + "reqd": 1, + "set_only_once": 1 + } + ], + "istable": 1, + "modified": "2019-06-03 12:52:32.267392", + "modified_by": "Administrator", + "module": "Education", + "name": "Quiz Result", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/quiz_result/quiz_result.py b/education/education/doctype/quiz_result/quiz_result.py new file mode 100644 index 0000000..3aeca12 --- /dev/null +++ b/education/education/doctype/quiz_result/quiz_result.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class QuizResult(Document): + pass diff --git a/education/education/doctype/quiz_result/test_quiz_result.py b/education/education/doctype/quiz_result/test_quiz_result.py new file mode 100644 index 0000000..bba530b --- /dev/null +++ b/education/education/doctype/quiz_result/test_quiz_result.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestQuizResult(unittest.TestCase): + pass diff --git a/education/education/doctype/room/__init__.py b/education/education/doctype/room/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/room/room.js b/education/education/doctype/room/room.js new file mode 100644 index 0000000..1263b60 --- /dev/null +++ b/education/education/doctype/room/room.js @@ -0,0 +1,2 @@ +frappe.ui.form.on("Room", { +}); diff --git a/education/education/doctype/room/room.json b/education/education/doctype/room/room.json new file mode 100644 index 0000000..888a0fa --- /dev/null +++ b/education/education/doctype/room/room.json @@ -0,0 +1,161 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "HTL-ROOM-.YYYY.-.#####", + "beta": 0, + "creation": "2015-09-09 16:20:14.613061", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "room_name", + "fieldtype": "Data", + "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": "Room Name", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "room_number", + "fieldtype": "Data", + "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": "Room Number", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "seating_capacity", + "fieldtype": "Data", + "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": "Seating Capacity", + "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 + } + ], + "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, + "menu_index": 0, + "modified": "2018-08-21 16:15:46.096492", + "modified_by": "Administrator", + "module": "Education", + "name": "Room", + "name_case": "", + "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, + "write": 1 + } + ], + "quick_entry": 1, + "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": "room_name", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/room/room.py b/education/education/doctype/room/room.py new file mode 100644 index 0000000..3c71fd1 --- /dev/null +++ b/education/education/doctype/room/room.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class Room(Document): + pass diff --git a/education/education/doctype/room/room_dashboard.py b/education/education/doctype/room/room_dashboard.py new file mode 100644 index 0000000..53776cf --- /dev/null +++ b/education/education/doctype/room/room_dashboard.py @@ -0,0 +1,14 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "room", + "transactions": [ + {"label": _("Course"), "items": ["Course Schedule"]}, + {"label": _("Assessment"), "items": ["Assessment Plan"]}, + ], + } diff --git a/education/education/doctype/room/test_records.json b/education/education/doctype/room/test_records.json new file mode 100644 index 0000000..6edf076 --- /dev/null +++ b/education/education/doctype/room/test_records.json @@ -0,0 +1,17 @@ +[ + { + "room_name": "_Test Room", + "room_number": "1001", + "seating_capacity": 100 + }, + { + "room_name": "_Test Room 1", + "room_number": "1002", + "seating_capacity": 100 + }, + { + "room_name": "_Test Room 2", + "room_number": "1003", + "seating_capacity": 100 + } +] \ No newline at end of file diff --git a/education/education/doctype/room/test_room.py b/education/education/doctype/room/test_room.py new file mode 100644 index 0000000..e590a7e --- /dev/null +++ b/education/education/doctype/room/test_room.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Room') + + +class TestRoom(unittest.TestCase): + pass diff --git a/education/education/doctype/school_house/__init__.py b/education/education/doctype/school_house/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/school_house/school_house.js b/education/education/doctype/school_house/school_house.js new file mode 100644 index 0000000..37a8d8d --- /dev/null +++ b/education/education/doctype/school_house/school_house.js @@ -0,0 +1,8 @@ +// Copyright (c) 2017, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('School House', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/school_house/school_house.json b/education/education/doctype/school_house/school_house.json new file mode 100644 index 0000000..8a653a9 --- /dev/null +++ b/education/education/doctype/school_house/school_house.json @@ -0,0 +1,94 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:house_name", + "beta": 0, + "creation": "2017-03-27 15:19:54.672995", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "house_name", + "fieldtype": "Data", + "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": "House Name", + "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, + "unique": 0 + } + ], + "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": "2017-11-10 19:05:06.419022", + "modified_by": "Administrator", + "module": "Education", + "name": "School House", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/school_house/school_house.py b/education/education/doctype/school_house/school_house.py new file mode 100644 index 0000000..b58629b --- /dev/null +++ b/education/education/doctype/school_house/school_house.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class SchoolHouse(Document): + pass diff --git a/education/education/doctype/school_house/test_school_house.py b/education/education/doctype/school_house/test_school_house.py new file mode 100644 index 0000000..31d0d87 --- /dev/null +++ b/education/education/doctype/school_house/test_school_house.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestSchoolHouse(unittest.TestCase): + pass diff --git a/education/education/doctype/student/__init__.py b/education/education/doctype/student/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student/student.js b/education/education/doctype/student/student.js new file mode 100644 index 0000000..098b59b --- /dev/null +++ b/education/education/doctype/student/student.js @@ -0,0 +1,63 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student', { + setup: function(frm) { + frm.add_fetch('guardian', 'guardian_name', 'guardian_name'); + frm.add_fetch('student', 'title', 'full_name'); + frm.add_fetch('student', 'gender', 'gender'); + frm.add_fetch('student', 'date_of_birth', 'date_of_birth'); + + frm.set_query('student', 'siblings', function(doc) { + return { + 'filters': { + 'name': ['!=', doc.name] + } + }; + }) + }, + refresh: function(frm) { + if(!frm.is_new()) { + + // custom buttons + frm.add_custom_button(__('Accounting Ledger'), function() { + frappe.set_route('query-report', 'General Ledger', + {party_type:'Student', party:frm.doc.name}); + }); + } + + frappe.db.get_value('Education Settings', {name: 'Education Settings'}, 'user_creation_skip', (r) => { + if (cint(r.user_creation_skip) !== 1) { + frm.set_df_property('student_email_id', 'reqd', 1); + } + }); + } +}); + +frappe.ui.form.on('Student Guardian', { + guardians_add: function(frm){ + frm.fields_dict['guardians'].grid.get_field('guardian').get_query = function(doc){ + let guardian_list = []; + if(!doc.__islocal) guardian_list.push(doc.guardian); + $.each(doc.guardians, function(idx, val){ + if (val.guardian) guardian_list.push(val.guardian); + }); + return { filters: [['Guardian', 'name', 'not in', guardian_list]] }; + }; + } +}); + + +frappe.ui.form.on('Student Sibling', { + siblings_add: function(frm){ + frm.fields_dict['siblings'].grid.get_field('student').get_query = function(doc){ + let sibling_list = [frm.doc.name]; + $.each(doc.siblings, function(idx, val){ + if (val.student && val.studying_in_same_institute == 'YES') { + sibling_list.push(val.student); + } + }); + return { filters: [['Student', 'name', 'not in', sibling_list]] }; + }; + } +}); diff --git a/education/education/doctype/student/student.json b/education/education/doctype/student/student.json new file mode 100644 index 0000000..8ba9a7f --- /dev/null +++ b/education/education/doctype/student/student.json @@ -0,0 +1,304 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2015-09-07 13:00:55.938280", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "section_break_1", + "enabled", + "section_break_3", + "first_name", + "middle_name", + "last_name", + "user", + "column_break_4", + "naming_series", + "student_email_id", + "student_mobile_number", + "joining_date", + "image", + "section_break_7", + "date_of_birth", + "blood_group", + "column_break_3", + "gender", + "nationality", + "student_applicant", + "section_break_22", + "address_line_1", + "address_line_2", + "pincode", + "column_break_20", + "city", + "state", + "section_break_18", + "guardians", + "section_break_20", + "siblings", + "exit", + "date_of_leaving", + "leaving_certificate_number", + "column_break_31", + "reason_for_leaving", + "title" + ], + "fields": [ + { + "fieldname": "section_break_1", + "fieldtype": "Section Break" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "first_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "First Name", + "reqd": 1 + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name" + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Last Name" + }, + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User ID", + "options": "User" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "no_copy": 1, + "options": "EDU-STU-.YYYY.-", + "set_only_once": 1 + }, + { + "fieldname": "student_email_id", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Student Email Address", + "unique": 1 + }, + { + "fieldname": "student_mobile_number", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Student Mobile Number" + }, + { + "default": "Today", + "fieldname": "joining_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Joining Date" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "width": "10" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Personal Details" + }, + { + "fieldname": "date_of_birth", + "fieldtype": "Date", + "label": "Date of Birth" + }, + { + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender" + }, + { + "fieldname": "nationality", + "fieldtype": "Data", + "label": "Nationality" + }, + { + "fieldname": "student_applicant", + "fieldtype": "Link", + "label": "Student Applicant", + "options": "Student Applicant", + "read_only": 1 + }, + { + "fieldname": "section_break_22", + "fieldtype": "Section Break", + "label": "Home Address" + }, + { + "fieldname": "address_line_1", + "fieldtype": "Data", + "label": "Address Line 1" + }, + { + "fieldname": "address_line_2", + "fieldtype": "Data", + "label": "Address Line 2" + }, + { + "fieldname": "pincode", + "fieldtype": "Data", + "label": "Pincode" + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "city", + "fieldtype": "Data", + "label": "City" + }, + { + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break", + "label": "Guardian Details" + }, + { + "fieldname": "guardians", + "fieldtype": "Table", + "label": "Guardians", + "options": "Student Guardian" + }, + { + "collapsible": 1, + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "label": "Sibling Details", + "options": "Country" + }, + { + "fieldname": "siblings", + "fieldtype": "Table", + "label": "Siblings", + "options": "Student Sibling" + }, + { + "collapsible": 1, + "fieldname": "exit", + "fieldtype": "Section Break", + "label": "Exit" + }, + { + "fieldname": "date_of_leaving", + "fieldtype": "Date", + "label": "Date of Leaving" + }, + { + "fieldname": "leaving_certificate_number", + "fieldtype": "Data", + "label": "Leaving Certificate Number" + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "fieldname": "reason_for_leaving", + "fieldtype": "Text", + "label": "Reason For Leaving" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title" + } + ], + "image_field": "image", + "links": [], + "modified": "2020-09-07 19:28:08.914568", + "modified_by": "Administrator", + "module": "Education", + "name": "Student", + "owner": "Administrator", + "permissions": [ + { + "read": 1, + "role": "Instructor" + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Student", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS User", + "share": 1 + } + ], + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title" +} \ No newline at end of file diff --git a/education/education/doctype/student/student.py b/education/education/doctype/student/student.py new file mode 100644 index 0000000..c6e7a90 --- /dev/null +++ b/education/education/doctype/student/student.py @@ -0,0 +1,232 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.desk.form.linked_with import get_linked_doctypes +from frappe.model.document import Document +from frappe.utils import getdate, today + +from education.education.utils import (check_content_completion, + check_quiz_completion) + + +class Student(Document): + def validate(self): + self.title = " ".join( + filter(None, [self.first_name, self.middle_name, self.last_name]) + ) + self.validate_dates() + + if self.student_applicant: + self.check_unique() + self.update_applicant_status() + + if frappe.get_value("Student", self.name, "title") != self.title: + self.update_student_name_in_linked_doctype() + + def validate_dates(self): + 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.")) + + if self.date_of_birth and getdate(self.date_of_birth) >= getdate(self.joining_date): + frappe.throw(_("Date of Birth cannot be greater than Joining Date.")) + + if ( + self.joining_date + and self.date_of_leaving + and getdate(self.joining_date) > getdate(self.date_of_leaving) + ): + frappe.throw(_("Joining Date can not be greater than Leaving Date")) + + def update_student_name_in_linked_doctype(self): + linked_doctypes = get_linked_doctypes("Student") + for d in linked_doctypes: + meta = frappe.get_meta(d) + if not meta.issingle: + if "student_name" in [f.fieldname for f in meta.fields]: + frappe.db.sql( + """UPDATE `tab{0}` set student_name = %s where {1} = %s""".format( + d, linked_doctypes[d]["fieldname"][0] + ), + (self.title, self.name), + ) + + if "child_doctype" in linked_doctypes[d].keys() and "student_name" in [ + f.fieldname for f in frappe.get_meta(linked_doctypes[d]["child_doctype"]).fields + ]: + frappe.db.sql( + """UPDATE `tab{0}` set student_name = %s where {1} = %s""".format( + linked_doctypes[d]["child_doctype"], linked_doctypes[d]["fieldname"][0] + ), + (self.title, self.name), + ) + + def check_unique(self): + """Validates if the Student Applicant is Unique""" + student = frappe.db.sql( + "select name from `tabStudent` where student_applicant=%s and name!=%s", + (self.student_applicant, self.name), + ) + if student: + frappe.throw( + _("Student {0} exist against student applicant {1}").format( + student[0][0], self.student_applicant + ) + ) + + def after_insert(self): + if not frappe.get_single("Education Settings").get("user_creation_skip"): + self.create_student_user() + + def create_student_user(self): + """Create a website user for student creation if not already exists""" + if not frappe.db.exists("User", self.student_email_id): + student_user = frappe.get_doc( + { + "doctype": "User", + "first_name": self.first_name, + "last_name": self.last_name, + "email": self.student_email_id, + "gender": self.gender, + "send_welcome_email": 1, + "user_type": "Website User", + } + ) + student_user.flags.ignore_permissions = True + student_user.add_roles("Student") + student_user.save() + + def update_applicant_status(self): + """Updates Student Applicant status to Admitted""" + if self.student_applicant: + frappe.db.set_value( + "Student Applicant", self.student_applicant, "application_status", "Admitted" + ) + + def get_all_course_enrollments(self): + """Returns a list of course enrollments linked with the current student""" + course_enrollments = frappe.get_all( + "Course Enrollment", filters={"student": self.name}, fields=["course", "name"] + ) + if not course_enrollments: + return None + else: + enrollments = {item["course"]: item["name"] for item in course_enrollments} + return enrollments + + def get_program_enrollments(self): + """Returns a list of course enrollments linked with the current student""" + program_enrollments = frappe.get_all( + "Program Enrollment", filters={"student": self.name}, fields=["program"] + ) + if not program_enrollments: + return None + else: + enrollments = [item["program"] for item in program_enrollments] + return enrollments + + def get_topic_progress(self, course_enrollment_name, topic): + """ + Get Progress Dictionary of a student for a particular topic + :param self: Student Object + :param course_enrollment_name: Name of the Course Enrollment + :param topic: Topic DocType Object + """ + contents = topic.get_contents() + progress = [] + if contents: + for content in contents: + if content.doctype in ("Article", "Video"): + status = check_content_completion( + content.name, content.doctype, course_enrollment_name + ) + progress.append( + {"content": content.name, "content_type": content.doctype, "is_complete": status} + ) + elif content.doctype == "Quiz": + status, score, result, time_taken = check_quiz_completion( + content, course_enrollment_name + ) + progress.append( + { + "content": content.name, + "content_type": content.doctype, + "is_complete": status, + "score": score, + "result": result, + } + ) + return progress + + def enroll_in_program(self, program_name): + try: + enrollment = frappe.get_doc( + { + "doctype": "Program Enrollment", + "student": self.name, + "academic_year": frappe.get_last_doc("Academic Year").name, + "program": program_name, + "enrollment_date": frappe.utils.datetime.datetime.now(), + } + ) + enrollment.save(ignore_permissions=True) + except frappe.exceptions.ValidationError: + enrollment_name = frappe.get_list( + "Program Enrollment", filters={"student": self.name, "Program": program_name} + )[0].name + return frappe.get_doc("Program Enrollment", enrollment_name) + else: + enrollment.submit() + return enrollment + + def enroll_in_course(self, course_name, program_enrollment, enrollment_date=None): + if enrollment_date is None: + enrollment_date = frappe.utils.datetime.datetime.now() + try: + enrollment = frappe.get_doc( + { + "doctype": "Course Enrollment", + "student": self.name, + "course": course_name, + "program_enrollment": program_enrollment, + "enrollment_date": enrollment_date, + } + ) + enrollment.save(ignore_permissions=True) + except frappe.exceptions.ValidationError: + enrollment_name = frappe.get_list( + "Course Enrollment", + filters={ + "student": self.name, + "course": course_name, + "program_enrollment": program_enrollment, + }, + )[0].name + return frappe.get_doc("Course Enrollment", enrollment_name) + else: + return enrollment + + +def get_timeline_data(doctype, name): + """Return timeline for attendance""" + return dict( + frappe.db.sql( + """select unix_timestamp(`date`), count(*) + from `tabStudent Attendance` where + student=%s + and `date` > date_sub(curdate(), interval 1 year) + and docstatus = 1 and status = 'Present' + group by date""", + name, + ) + ) diff --git a/education/education/doctype/student/student_dashboard.py b/education/education/doctype/student/student_dashboard.py new file mode 100644 index 0000000..4641a03 --- /dev/null +++ b/education/education/doctype/student/student_dashboard.py @@ -0,0 +1,27 @@ +from frappe import _ + + +def get_data(): + return { + "heatmap": True, + "heatmap_message": _("This is based on the attendance of this Student"), + "fieldname": "student", + "non_standard_fieldnames": {"Bank Account": "party"}, + "transactions": [ + {"label": _("Admission"), "items": ["Program Enrollment", "Course Enrollment"]}, + { + "label": _("Student Activity"), + "items": [ + "Student Log", + "Student Group", + ], + }, + {"label": _("Assessment"), "items": ["Assessment Result"]}, + {"label": _("Student LMS Activity"), "items": ["Course Activity", "Quiz Activity"]}, + { + "label": _("Attendance"), + "items": ["Student Attendance", "Student Leave Application"], + }, + {"label": _("Fee"), "items": ["Fees", "Bank Account"]}, + ], + } diff --git a/education/education/doctype/student/student_list.js b/education/education/doctype/student/student_list.js new file mode 100644 index 0000000..c1bce24 --- /dev/null +++ b/education/education/doctype/student/student_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings['Student'] = { + add_fields: [ "image"] +} diff --git a/education/education/doctype/student/test_records.json b/education/education/doctype/student/test_records.json new file mode 100644 index 0000000..8ad3afa --- /dev/null +++ b/education/education/doctype/student/test_records.json @@ -0,0 +1,35 @@ +[ + { + "first_name": "_Test", + "middle_name": "Student", + "last_name": "Name", + "program": "TC101", + "date_of_birth": "2000-01-01", + "gender": "Male", + "student_email_id": "_test_student@example.com", + "blood_group": "A+" + + }, + { + "first_name": "_Test", + "middle_name": "Student", + "last_name": "Name 1", + "program": "TC101", + "date_of_birth": "2000-01-01", + "gender": "Male", + "student_email_id": "_test_student_1@example.com", + "blood_group": "A+" + + }, + { + "first_name": "_Test", + "middle_name": "Student", + "last_name": "Name 2", + "program": "TC101", + "date_of_birth": "2000-01-01", + "gender": "Female", + "student_email_id": "_test_student_2@example.com", + "blood_group": "A+" + + } +] \ No newline at end of file diff --git a/education/education/doctype/student/test_student.py b/education/education/doctype/student/test_student.py new file mode 100644 index 0000000..7df6889 --- /dev/null +++ b/education/education/doctype/student/test_student.py @@ -0,0 +1,85 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe + +from education.education.doctype.program.test_program import \ + make_program_and_linked_courses + +test_records = frappe.get_test_records("Student") + + +class TestStudent(unittest.TestCase): + def setUp(self): + create_student( + { + "first_name": "_Test Name", + "last_name": "_Test Last Name", + "email": "_test_student@example.com", + } + ) + make_program_and_linked_courses( + "_Test Program 1", ["_Test Course 1", "_Test Course 2"] + ) + + def test_create_student_user(self): + self.assertTrue(bool(frappe.db.exists("User", "_test_student@example.com"))) + frappe.db.rollback() + + def test_enroll_in_program(self): + student = get_student("_test_student@example.com") + enrollment = student.enroll_in_program("_Test Program 1") + test_enrollment = frappe.get_all( + "Program Enrollment", filters={"student": student.name, "Program": "_Test Program 1"} + ) + self.assertTrue(len(test_enrollment)) + self.assertEqual(test_enrollment[0]["name"], enrollment.name) + frappe.db.rollback() + + def test_get_program_enrollments(self): + student = get_student("_test_student@example.com") + enrollment = student.enroll_in_program("_Test Program 1") + program_enrollments = student.get_program_enrollments() + self.assertTrue("_Test Program 1" in program_enrollments) + frappe.db.rollback() + + def test_get_all_course_enrollments(self): + student = get_student("_test_student@example.com") + enrollment = student.enroll_in_program("_Test Program 1") + course_enrollments = student.get_all_course_enrollments() + self.assertTrue("_Test Course 1" in course_enrollments.keys()) + self.assertTrue("_Test Course 2" in course_enrollments.keys()) + frappe.db.rollback() + + def tearDown(self): + for entry in frappe.db.get_all("Course Enrollment"): + frappe.delete_doc("Course Enrollment", entry.name) + + for entry in frappe.db.get_all("Program Enrollment"): + doc = frappe.get_doc("Program Enrollment", entry.name) + doc.cancel() + doc.delete() + + +def create_student(student_dict): + student = get_student(student_dict["email"]) + if not student: + student = frappe.get_doc( + { + "doctype": "Student", + "first_name": student_dict["first_name"], + "last_name": student_dict["last_name"], + "student_email_id": student_dict["email"], + } + ).insert() + return student + + +def get_student(email): + try: + student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name + return frappe.get_doc("Student", student_id) + except IndexError: + return None diff --git a/education/education/doctype/student_admission/__init__.py b/education/education/doctype/student_admission/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_admission/student_admission.js b/education/education/doctype/student_admission/student_admission.js new file mode 100644 index 0000000..1ba37ea --- /dev/null +++ b/education/education/doctype/student_admission/student_admission.js @@ -0,0 +1,22 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Admission', { + program: function(frm) { + if (frm.doc.academic_year && frm.doc.program) { + frm.doc.route = frappe.model.scrub(frm.doc.program) + "-" + frappe.model.scrub(frm.doc.academic_year) + frm.refresh_field("route"); + } + }, + + academic_year: function(frm) { + frm.trigger("program"); + }, + + admission_end_date: function(frm) { + if(frm.doc.admission_end_date && frm.doc.admission_end_date <= frm.doc.admission_start_date){ + frm.set_value("admission_end_date", ""); + frappe.throw(__("Admission End Date should be greater than Admission Start Date.")); + } + } +}); diff --git a/education/education/doctype/student_admission/student_admission.json b/education/education/doctype/student_admission/student_admission.json new file mode 100644 index 0000000..75f2162 --- /dev/null +++ b/education/education/doctype/student_admission/student_admission.json @@ -0,0 +1,122 @@ +{ + "actions": [], + "allow_guest_to_view": 1, + "allow_rename": 1, + "creation": "2016-09-13 03:05:27.154713", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "route", + "column_break_3", + "academic_year", + "admission_start_date", + "admission_end_date", + "published", + "enable_admission_application", + "section_break_5", + "program_details", + "introduction" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title" + }, + { + "fieldname": "route", + "fieldtype": "Data", + "label": "Route", + "no_copy": 1, + "unique": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "academic_year", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Academic Year", + "no_copy": 1, + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname": "admission_start_date", + "fieldtype": "Date", + "label": "Admission Start Date", + "mandatory_depends_on": "enable_admission_application", + "no_copy": 1 + }, + { + "fieldname": "admission_end_date", + "fieldtype": "Date", + "label": "Admission End Date", + "mandatory_depends_on": "enable_admission_application", + "no_copy": 1 + }, + { + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Publish on website" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Eligibility and Details" + }, + { + "fieldname": "program_details", + "fieldtype": "Table", + "label": "Eligibility and Details", + "options": "Student Admission Program" + }, + { + "fieldname": "introduction", + "fieldtype": "Text Editor", + "label": "Introduction" + }, + { + "default": "0", + "depends_on": "published", + "fieldname": "enable_admission_application", + "fieldtype": "Check", + "label": "Enable Admission Application" + } + ], + "has_web_view": 1, + "is_published_field": "published", + "links": [], + "modified": "2020-09-18 00:14:54.615321", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Admission", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "route": "admissions", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title" +} \ No newline at end of file diff --git a/education/education/doctype/student_admission/student_admission.py b/education/education/doctype/student_admission/student_admission.py new file mode 100644 index 0000000..a999222 --- /dev/null +++ b/education/education/doctype/student_admission/student_admission.py @@ -0,0 +1,59 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.utils import nowdate +from frappe.website.website_generator import WebsiteGenerator + + +class StudentAdmission(WebsiteGenerator): + def autoname(self): + if not self.title: + self.title = self.get_title() + self.name = self.title + + def validate(self): + if not self.route: # pylint: disable=E0203 + self.route = "admissions/" + "-".join(self.title.split(" ")) + + if self.enable_admission_application and not self.program_details: + frappe.throw(_("Please add programs to enable admission application.")) + + def get_context(self, context): + context.no_cache = 1 + context.show_sidebar = True + context.title = self.title + context.parents = [ + {"name": "admissions", "title": _("All Student Admissions"), "route": "admissions"} + ] + + def get_title(self): + return _("Admissions for {0}").format(self.academic_year) + + +def get_list_context(context=None): + context.update( + { + "show_sidebar": True, + "title": _("Student Admissions"), + "get_list": get_admission_list, + "row_template": "education/doctype/student_admission/templates/student_admission_row.html", + } + ) + + +def get_admission_list( + doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified" +): + return frappe.db.sql( + """select name, title, academic_year, modified, admission_start_date, route, + admission_end_date from `tabStudent Admission` where published=1 and admission_end_date >= %s + order by admission_end_date asc limit {0}, {1} + """.format( + limit_start, limit_page_length + ), + [nowdate()], + as_dict=1, + ) diff --git a/education/education/doctype/student_admission/templates/student_admission.html b/education/education/doctype/student_admission/templates/student_admission.html new file mode 100644 index 0000000..f9ddac0 --- /dev/null +++ b/education/education/doctype/student_admission/templates/student_admission.html @@ -0,0 +1,77 @@ + +{% extends "templates/web.html" %} + +{% block breadcrumbs %} + {% include "templates/includes/breadcrumbs.html" %} +{% endblock %} + +{% block header %} +

{{ title }}

+{% endblock %} + +{% block page_content %} + {% set today = frappe.utils.getdate(frappe.utils.nowdate()) %} +
+
+ Application will be closed soon + {% elif frappe.utils.getdate(doc.admission_end_date) > today >= frappe.utils.getdate(doc.admission_start_date)%} + green"> Application open + {% elif frappe.utils.getdate(doc.admission_start_date) > today %} + blue"> Application will open + {% else %} + gray + {% endif %} + +
+
+ {{ _("Start on") }}: {{ frappe.format_date(admission_start_date) }}
+ {{ _("End on") }}: {{ frappe.format_date(admission_end_date) }} +
+

+ + {%- if introduction -%} +
{{ introduction }}
+ {% endif %} + + {% if program_details %} +
+
+

Eligibility and Other Details:

+ + + + + + + + + {%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%} + + {% endif %} + + + + {% for row in program_details %} + + + + + + + {%- if doc.enable_admission_application and frappe.utils.getdate(doc.admission_start_date) <= today -%} + + {% endif %} + + {% endfor %} + +
Program/Std.DescriptionMinumum AgeMaximum AgeApplication Fee
{{ row.program }}
{{ row.description if row.description else '' }}
{{ row.min_age }}{{ row.max_age }}{{ row.application_fee }} + + {{ _("Apply Now") }} + +
+
+ {% endif %} + +{% endblock %} diff --git a/education/education/doctype/student_admission/templates/student_admission_row.html b/education/education/doctype/student_admission/templates/student_admission_row.html new file mode 100644 index 0000000..dc4587b --- /dev/null +++ b/education/education/doctype/student_admission/templates/student_admission_row.html @@ -0,0 +1,44 @@ + diff --git a/education/education/doctype/student_admission/test_student_admission.py b/education/education/doctype/student_admission/test_student_admission.py new file mode 100644 index 0000000..448ad3c --- /dev/null +++ b/education/education/doctype/student_admission/test_student_admission.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Admission') + + +class TestStudentAdmission(unittest.TestCase): + pass diff --git a/education/education/doctype/student_admission_program/__init__.py b/education/education/doctype/student_admission_program/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_admission_program/student_admission_program.json b/education/education/doctype/student_admission_program/student_admission_program.json new file mode 100644 index 0000000..d14b9a4 --- /dev/null +++ b/education/education/doctype/student_admission_program/student_admission_program.json @@ -0,0 +1,73 @@ +{ + "actions": [], + "creation": "2017-09-15 12:59:43.207923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "program", + "min_age", + "max_age", + "description", + "column_break_4", + "application_fee", + "applicant_naming_series" + ], + "fields": [ + { + "fieldname": "program", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Program", + "options": "Program" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "application_fee", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Application Fee" + }, + { + "fieldname": "applicant_naming_series", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Naming Series (for Student Applicant)" + }, + { + "fieldname": "min_age", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Minimum Age" + }, + { + "fieldname": "max_age", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Maximum Age" + }, + { + "fetch_from": "program.description", + "fetch_if_empty": 1, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + } + ], + "istable": 1, + "links": [], + "modified": "2020-10-05 13:03:42.005985", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Admission Program", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/student_admission_program/student_admission_program.py b/education/education/doctype/student_admission_program/student_admission_program.py new file mode 100644 index 0000000..da27c7e --- /dev/null +++ b/education/education/doctype/student_admission_program/student_admission_program.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentAdmissionProgram(Document): + pass diff --git a/education/education/doctype/student_applicant/__init__.py b/education/education/doctype/student_applicant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_applicant/student_applicant.js b/education/education/doctype/student_applicant/student_applicant.js new file mode 100644 index 0000000..2319e93 --- /dev/null +++ b/education/education/doctype/student_applicant/student_applicant.js @@ -0,0 +1,62 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on("Student Applicant", { + setup: function(frm) { + frm.add_fetch("guardian", "guardian_name", "guardian_name"); + }, + + refresh: function(frm) { + if (frm.doc.application_status==="Applied" && frm.doc.docstatus===1 ) { + frm.add_custom_button(__("Approve"), function() { + frm.set_value("application_status", "Approved"); + frm.save_or_update(); + + }, 'Actions'); + + frm.add_custom_button(__("Reject"), function() { + frm.set_value("application_status", "Rejected"); + frm.save_or_update(); + }, 'Actions'); + } + + if (frm.doc.application_status === "Approved" && frm.doc.docstatus === 1) { + frm.add_custom_button(__("Enroll"), function() { + frm.events.enroll(frm) + }).addClass("btn-primary"); + + frm.add_custom_button(__("Reject"), function() { + frm.set_value("application_status", "Rejected"); + frm.save_or_update(); + }, 'Actions'); + } + + frappe.realtime.on("enroll_student_progress", function(data) { + if(data.progress) { + frappe.hide_msgprint(true); + frappe.show_progress(__("Enrolling student"), data.progress[0],data.progress[1]); + } + }); + + frappe.db.get_value("Education Settings", {name: "Education Settings"}, "user_creation_skip", (r) => { + if (cint(r.user_creation_skip) !== 1) { + frm.set_df_property("student_email_id", "reqd", 1); + } + }); + }, + + enroll: function(frm) { + frappe.model.open_mapped_doc({ + method: "education.education.api.enroll_student", + frm: frm + }) + } +}); + +frappe.ui.form.on('Student Sibling', { + setup: function(frm) { + frm.add_fetch("student", "title", "full_name"); + frm.add_fetch("student", "gender", "gender"); + frm.add_fetch("student", "date_of_birth", "date_of_birth"); + } +}); diff --git a/education/education/doctype/student_applicant/student_applicant.json b/education/education/doctype/student_applicant/student_applicant.json new file mode 100644 index 0000000..95f9224 --- /dev/null +++ b/education/education/doctype/student_applicant/student_applicant.json @@ -0,0 +1,300 @@ +{ + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2015-09-11 11:50:09.740807", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "first_name", + "middle_name", + "last_name", + "program", + "student_category", + "lms_only", + "paid", + "column_break_8", + "naming_series", + "application_status", + "application_date", + "academic_year", + "academic_term", + "student_admission", + "image", + "section_break_4", + "date_of_birth", + "gender", + "blood_group", + "column_break_12", + "student_email_id", + "student_mobile_number", + "nationality", + "home_address", + "address_line_1", + "address_line_2", + "pincode", + "column_break_23", + "city", + "state", + "section_break_20", + "guardians", + "section_break_21", + "siblings", + "section_break_23", + "title", + "amended_from" + ], + "fields": [ + { + "fieldname": "first_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "First Name", + "reqd": 1 + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name" + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Last Name" + }, + { + "fieldname": "program", + "fieldtype": "Link", + "in_filter": 1, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Program", + "options": "Program", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "lms_only", + "fieldtype": "Check", + "label": "LMS Only" + }, + { + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "label": "Paid" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "no_copy": 1, + "options": "EDU-APP-.YYYY.-", + "set_only_once": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.docstatus != 0", + "fieldname": "application_status", + "fieldtype": "Select", + "in_filter": 1, + "label": "Application Status", + "no_copy": 1, + "options": "Applied\nApproved\nRejected\nAdmitted" + }, + { + "default": "Today", + "fieldname": "application_date", + "fieldtype": "Date", + "label": "Application Date" + }, + { + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year" + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "student_admission", + "fieldtype": "Link", + "label": "Student Admission", + "options": "Student Admission" + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Personal Details" + }, + { + "fieldname": "date_of_birth", + "fieldtype": "Date", + "label": "Date of Birth" + }, + { + "fieldname": "gender", + "fieldtype": "Select", + "label": "Gender", + "options": "\nMale\nFemale" + }, + { + "fieldname": "blood_group", + "fieldtype": "Select", + "label": "Blood Group", + "options": "\nA+\nA-\nB+\nB-\nO+\nO-\nAB+\nAB-" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "student_email_id", + "fieldtype": "Data", + "label": "Student Email Address", + "options": "Email", + "unique": 1 + }, + { + "fieldname": "student_mobile_number", + "fieldtype": "Data", + "label": "Student Mobile Number" + }, + { + "fieldname": "nationality", + "fieldtype": "Data", + "label": "Nationality" + }, + { + "fieldname": "home_address", + "fieldtype": "Section Break", + "label": "Home Address" + }, + { + "fieldname": "address_line_1", + "fieldtype": "Data", + "label": "Address Line 1" + }, + { + "fieldname": "address_line_2", + "fieldtype": "Data", + "label": "Address Line 2" + }, + { + "fieldname": "pincode", + "fieldtype": "Data", + "label": "Pincode" + }, + { + "fieldname": "column_break_23", + "fieldtype": "Column Break" + }, + { + "fieldname": "city", + "fieldtype": "Data", + "label": "City" + }, + { + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "collapsible": 1, + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "label": "Guardian Details" + }, + { + "fieldname": "guardians", + "fieldtype": "Table", + "label": "Guardians", + "options": "Student Guardian" + }, + { + "collapsible": 1, + "fieldname": "section_break_21", + "fieldtype": "Section Break", + "label": "Sibling Details" + }, + { + "fieldname": "siblings", + "fieldtype": "Table", + "label": "Siblings", + "options": "Student Sibling" + }, + { + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Student Applicant", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category" + } + ], + "image_field": "image", + "is_submittable": 1, + "links": [], + "modified": "2021-03-01 23:00:25.119241", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Applicant", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "title" +} \ No newline at end of file diff --git a/education/education/doctype/student_applicant/student_applicant.py b/education/education/doctype/student_applicant/student_applicant.py new file mode 100644 index 0000000..2ca429a --- /dev/null +++ b/education/education/doctype/student_applicant/student_applicant.py @@ -0,0 +1,108 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import add_years, date_diff, getdate, nowdate + + +class StudentApplicant(Document): + def autoname(self): + from frappe.model.naming import set_name_by_naming_series + + if self.student_admission: + naming_series = None + if self.program: + # set the naming series from the student admission if provided. + student_admission = get_student_admission_data(self.student_admission, self.program) + if student_admission: + naming_series = student_admission.get("applicant_naming_series") + else: + naming_series = None + else: + frappe.throw(_("Select the program first")) + + if naming_series: + self.naming_series = naming_series + + set_name_by_naming_series(self) + + def validate(self): + self.validate_dates() + self.title = " ".join( + filter(None, [self.first_name, self.middle_name, self.last_name]) + ) + + if self.student_admission and self.program and self.date_of_birth: + self.validation_from_student_admission() + + def validate_dates(self): + if self.date_of_birth and getdate(self.date_of_birth) >= getdate(): + frappe.throw(_("Date of Birth cannot be greater than today.")) + + def on_update_after_submit(self): + student = frappe.get_list("Student", filters={"student_applicant": self.name}) + if student: + frappe.throw( + _( + "Cannot change status as student {0} is linked with student application {1}" + ).format(student[0].name, self.name) + ) + + def on_submit(self): + if self.paid and not self.student_admission: + frappe.throw( + _( + "Please select Student Admission which is mandatory for the paid student applicant" + ) + ) + + def validation_from_student_admission(self): + + student_admission = get_student_admission_data(self.student_admission, self.program) + + if ( + student_admission + and student_admission.min_age + and date_diff( + nowdate(), add_years(getdate(self.date_of_birth), student_admission.min_age) + ) + < 0 + ): + frappe.throw( + _("Not eligible for the admission in this program as per Date Of Birth") + ) + + if ( + student_admission + and student_admission.max_age + and date_diff( + nowdate(), add_years(getdate(self.date_of_birth), student_admission.max_age) + ) + > 0 + ): + frappe.throw( + _("Not eligible for the admission in this program as per Date Of Birth") + ) + + def on_payment_authorized(self, *args, **kwargs): + self.db_set("paid", 1) + + +def get_student_admission_data(student_admission, program): + + student_admission = frappe.db.sql( + """select sa.admission_start_date, sa.admission_end_date, + sap.program, sap.min_age, sap.max_age, sap.applicant_naming_series + from `tabStudent Admission` sa, `tabStudent Admission Program` sap + where sa.name = sap.parent and sa.name = %s and sap.program = %s""", + (student_admission, program), + as_dict=1, + ) + + if student_admission: + return student_admission[0] + else: + return None diff --git a/education/education/doctype/student_applicant/student_applicant_list.js b/education/education/doctype/student_applicant/student_applicant_list.js new file mode 100644 index 0000000..c39d46e --- /dev/null +++ b/education/education/doctype/student_applicant/student_applicant_list.js @@ -0,0 +1,21 @@ +frappe.listview_settings['Student Applicant'] = { + add_fields: [ "application_status", 'paid'], + has_indicator_for_draft: 1, + get_indicator: function(doc) { + if (doc.paid) { + return [__("Paid"), "green", "paid,=,Yes"]; + } + else if (doc.application_status=="Applied") { + return [__("Applied"), "orange", "application_status,=,Applied"]; + } + else if (doc.application_status=="Approved") { + return [__("Approved"), "green", "application_status,=,Approved"]; + } + else if (doc.application_status=="Rejected") { + return [__("Rejected"), "red", "application_status,=,Rejected"]; + } + else if (doc.application_status=="Admitted") { + return [__("Admitted"), "blue", "application_status,=,Admitted"]; + } + } +}; diff --git a/education/education/doctype/student_applicant/test_student_applicant.py b/education/education/doctype/student_applicant/test_student_applicant.py new file mode 100644 index 0000000..7efdf6b --- /dev/null +++ b/education/education/doctype/student_applicant/test_student_applicant.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Applicant') + + +class TestStudentApplicant(unittest.TestCase): + pass diff --git a/education/education/doctype/student_attendance/__init__.py b/education/education/doctype/student_attendance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_attendance/student_attendance.js b/education/education/doctype/student_attendance/student_attendance.js new file mode 100644 index 0000000..5236c97 --- /dev/null +++ b/education/education/doctype/student_attendance/student_attendance.js @@ -0,0 +1,5 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +cur_frm.add_fetch("course_schedule", "schedule_date", "date"); +cur_frm.add_fetch("course_schedule", "student_group", "student_group") diff --git a/education/education/doctype/student_attendance/student_attendance.json b/education/education/doctype/student_attendance/student_attendance.json new file mode 100644 index 0000000..e6e46d1 --- /dev/null +++ b/education/education/doctype/student_attendance/student_attendance.json @@ -0,0 +1,134 @@ +{ + "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", + "student_mobile_number", + "course_schedule", + "student_group", + "column_break_3", + "date", + "status", + "leave_application", + "amended_from" + ], + "fields": [ + { + "fieldname": "student", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Student", + "options": "Student", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "course_schedule", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course Schedule", + "options": "Course Schedule" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "label": "Date", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "student.title", + "fieldname": "student_name", + "fieldtype": "Read Only", + "in_global_search": 1, + "label": "Student Name" + }, + { + "fieldname": "student_group", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Student Group", + "options": "Student Group" + }, + { + "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 + }, + { + "fetch_from": "student.student_mobile_number", + "fieldname": "student_mobile_number", + "fieldtype": "Read Only", + "label": "Student Mobile Number", + "options": "Phone" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-03-24 00:02:11.005895", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Attendance", + "owner": "Administrator", + "permissions": [ + { + "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 + } + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "student_name" +} \ No newline at end of file diff --git a/education/education/doctype/student_attendance/student_attendance.py b/education/education/doctype/student_attendance/student_attendance.py new file mode 100644 index 0000000..ab57b9c --- /dev/null +++ b/education/education/doctype/student_attendance/student_attendance.py @@ -0,0 +1,142 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from erpnext import get_default_company +from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday +from frappe import _ +from frappe.model.document import Document +from frappe.utils import formatdate, get_link_to_form, getdate + +from education.education.api import get_student_group_students + + +class StudentAttendance(Document): + def validate(self): + self.validate_mandatory() + self.validate_date() + self.set_date() + self.set_student_group() + self.validate_student() + self.validate_duplication() + self.validate_is_holiday() + + def set_date(self): + if self.course_schedule: + 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( + _("{0} or {1} is mandatory").format( + frappe.bold("Student Group"), frappe.bold("Course Schedule") + ), + title=_("Mandatory Fields"), + ) + + def validate_date(self): + if not self.leave_application and getdate(self.date) > getdate(): + frappe.throw(_("Attendance cannot be marked for future dates.")) + + if self.student_group: + academic_year = frappe.db.get_value( + "Student Group", self.student_group, "academic_year" + ) + if academic_year: + year_start_date, year_end_date = frappe.db.get_value( + "Academic Year", academic_year, ["year_start_date", "year_end_date"] + ) + if year_start_date and year_end_date: + if getdate(self.date) < getdate(year_start_date) or getdate(self.date) > getdate( + year_end_date + ): + frappe.throw( + _("Attendance cannot be marked outside of Academic Year {0}").format( + academic_year + ) + ) + + def set_student_group(self): + if self.course_schedule: + 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" + ) + 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: + 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_record = None + if self.course_schedule: + attendance_record = frappe.db.exists( + "Student Attendance", + { + "student": self.student, + "course_schedule": self.course_schedule, + "docstatus": ("!=", 2), + "name": ("!=", self.name), + }, + ) + else: + 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("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 diff --git a/education/education/doctype/student_attendance/student_attendance_dashboard.py b/education/education/doctype/student_attendance/student_attendance_dashboard.py new file mode 100644 index 0000000..34132af --- /dev/null +++ b/education/education/doctype/student_attendance/student_attendance_dashboard.py @@ -0,0 +1,12 @@ +from frappe import _ + + +def get_data(): + return { + "reports": [ + { + "label": _("Reports"), + "items": ["Student Monthly Attendance Sheet", "Student Batch-Wise Attendance"], + } + ] + } diff --git a/education/education/doctype/student_attendance/student_attendance_list.js b/education/education/doctype/student_attendance/student_attendance_list.js new file mode 100644 index 0000000..e89b76c --- /dev/null +++ b/education/education/doctype/student_attendance/student_attendance_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings['Student Attendance'] = { + add_fields: [ "status"], + get_indicator: function(doc) { + if (doc.status=="Absent") { + return [__("Absent"), "orange", "status,=,Absent"]; + } + else if (doc.status=="Present") { + return [__("Present"), "green", "status,=,Present"]; + } + } +}; diff --git a/education/education/doctype/student_attendance/test_student_attendance.py b/education/education/doctype/student_attendance/test_student_attendance.py new file mode 100644 index 0000000..acba9a2 --- /dev/null +++ b/education/education/doctype/student_attendance/test_student_attendance.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Attendance') + + +class TestStudentAttendance(unittest.TestCase): + pass diff --git a/education/education/doctype/student_attendance_tool/__init__.py b/education/education/doctype/student_attendance_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_attendance_tool/student_attendance_tool.js b/education/education/doctype/student_attendance_tool/student_attendance_tool.js new file mode 100644 index 0000000..2937a13 --- /dev/null +++ b/education/education/doctype/student_attendance_tool/student_attendance_tool.js @@ -0,0 +1,196 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +frappe.provide("education"); + +frappe.ui.form.on('Student Attendance Tool', { + setup: (frm) => { + frm.students_area = $('
') + .appendTo(frm.fields_dict.students_html.wrapper); + }, + onload: function(frm) { + frm.set_query("student_group", function() { + return { + "filters": { + "group_based_on": frm.doc.group_based_on, + "disabled": 0 + } + }; + }); + }, + + refresh: function(frm) { + if (frappe.route_options) { + frm.set_value("based_on", frappe.route_options.based_on); + frm.set_value("student_group", frappe.route_options.student_group); + frm.set_value("course_schedule", frappe.route_options.course_schedule); + frappe.route_options = null; + } + frm.disable_save(); + }, + + based_on: function(frm) { + if (frm.doc.based_on == "Student Group") { + frm.set_value("course_schedule", ""); + } else { + frm.set_value("student_group", ""); + } + }, + + student_group: function(frm) { + if ((frm.doc.student_group && frm.doc.date) || frm.doc.course_schedule) { + frm.students_area.find('.student-attendance-checks').html(`
Fetching...
`); + var method = "education.education.doctype.student_attendance_tool.student_attendance_tool.get_student_attendance_records"; + + frappe.call({ + method: method, + args: { + based_on: frm.doc.based_on, + student_group: frm.doc.student_group, + date: frm.doc.date, + course_schedule: frm.doc.course_schedule + }, + callback: function(r) { + frm.events.get_students(frm, r.message); + } + }) + } + }, + + date: function(frm) { + if (frm.doc.date > frappe.datetime.get_today()) + frappe.throw(__("Cannot mark attendance for future dates.")); + frm.trigger("student_group"); + }, + + course_schedule: function(frm) { + frm.trigger("student_group"); + }, + + get_students: function(frm, students) { + students = students || []; + frm.students_editor = new education.StudentsEditor(frm, frm.students_area, students); + } +}); + + +education.StudentsEditor = class StudentsEditor { + constructor(frm, wrapper, students) { + this.wrapper = wrapper; + this.frm = frm; + if(students.length > 0) { + this.make(frm, students); + } else { + this.show_empty_state(); + } + } + make(frm, students) { + var me = this; + + $(this.wrapper).empty(); + var student_toolbar = $('

\ + \ + \ +

').appendTo($(this.wrapper)); + + student_toolbar.find(".btn-add") + .html(__('Check all')) + .on("click", function() { + $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { + if (!$(check).prop("disabled")) { + check.checked = true; + } + }); + }); + + student_toolbar.find(".btn-remove") + .html(__('Uncheck all')) + .on("click", function() { + $(me.wrapper).find('input[type="checkbox"]').each(function(i, check) { + if (!$(check).prop("disabled")) { + check.checked = false; + } + }); + }); + + student_toolbar.find(".btn-mark-att") + .html(__('Mark Attendance')) + .on("click", function() { + $(me.wrapper.find(".btn-mark-att")).attr("disabled", true); + var studs = []; + $(me.wrapper.find('input[type="checkbox"]')).each(function(i, check) { + var $check = $(check); + studs.push({ + student: $check.data().student, + student_name: $check.data().studentName, + group_roll_number: $check.data().group_roll_number, + disabled: $check.prop("disabled"), + checked: $check.is(":checked") + }); + }); + + var students_present = studs.filter(function(stud) { + return !stud.disabled && stud.checked; + }); + + var students_absent = studs.filter(function(stud) { + return !stud.disabled && !stud.checked; + }); + + frappe.confirm(__("Do you want to update attendance?
Present: {0}
Absent: {1}", + [students_present.length, students_absent.length]), + function() { //ifyes + if(!frappe.request.ajax_count) { + frappe.call({ + method: "education.education.api.mark_attendance", + freeze: true, + freeze_message: __("Marking attendance"), + args: { + "students_present": students_present, + "students_absent": students_absent, + "student_group": frm.doc.student_group, + "course_schedule": frm.doc.course_schedule, + "date": frm.doc.date + }, + callback: function(r) { + $(me.wrapper.find(".btn-mark-att")).attr("disabled", false); + frm.trigger("student_group"); + } + }); + } + }, + function() { //ifno + $(me.wrapper.find(".btn-mark-att")).attr("disabled", false); + } + ); + }); + + // make html grid of students + let student_html = ''; + for (let student of students) { + student_html += `
+
+ +
+
`; + } + + $(`
${student_html}
`).appendTo(me.wrapper); + } + + show_empty_state() { + $(this.wrapper).html( + `
+ ${__("No Students in")} ${this.frm.doc.student_group} +
` + ); + } +}; diff --git a/education/education/doctype/student_attendance_tool/student_attendance_tool.json b/education/education/doctype/student_attendance_tool/student_attendance_tool.json new file mode 100644 index 0000000..ee8f484 --- /dev/null +++ b/education/education/doctype/student_attendance_tool/student_attendance_tool.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_copy": 1, + "creation": "2016-11-16 17:12:46.437539", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "based_on", + "group_based_on", + "column_break_2", + "student_group", + "academic_year", + "academic_term", + "course_schedule", + "date", + "attendance", + "students_html" + ], + "fields": [ + { + "fieldname": "based_on", + "fieldtype": "Select", + "label": "Based On", + "options": "Student Group\nCourse Schedule" + }, + { + "default": "Batch", + "depends_on": "eval:doc.based_on == \"Student Group\"", + "fieldname": "group_based_on", + "fieldtype": "Select", + "label": "Group Based On", + "options": "Batch\nCourse\nActivity" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:doc.based_on ==\"Student Group\"", + "fieldname": "student_group", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Group", + "options": "Student Group", + "reqd": 1 + }, + { + "depends_on": "eval:doc.based_on ==\"Course Schedule\"", + "fieldname": "course_schedule", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course Schedule", + "options": "Course Schedule", + "reqd": 1 + }, + { + "depends_on": "eval:doc.based_on ==\"Student Group\"", + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "depends_on": "eval: (doc.course_schedule \n|| (doc.student_group && doc.date))", + "fieldname": "attendance", + "fieldtype": "Section Break", + "label": "Attendance" + }, + { + "fieldname": "students_html", + "fieldtype": "HTML", + "label": "Students HTML" + }, + { + "fetch_from": "student_group.academic_year", + "fieldname": "academic_year", + "fieldtype": "Link", + "label": "Academic Year", + "options": "Academic Year", + "read_only": 1 + }, + { + "fetch_from": "student_group.academic_term", + "fieldname": "academic_term", + "fieldtype": "Link", + "label": "Academic Term", + "options": "Academic Term", + "read_only": 1 + } + ], + "hide_toolbar": 1, + "issingle": 1, + "links": [], + "modified": "2020-10-23 17:52:28.078971", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Attendance Tool", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "read": 1, + "role": "Instructor", + "write": 1 + }, + { + "create": 1, + "read": 1, + "role": "Academics User", + "write": 1 + } + ], + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/student_attendance_tool/student_attendance_tool.py b/education/education/doctype/student_attendance_tool/student_attendance_tool.py new file mode 100644 index 0000000..248b7af --- /dev/null +++ b/education/education/doctype/student_attendance_tool/student_attendance_tool.py @@ -0,0 +1,67 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe.model.document import Document + + +class StudentAttendanceTool(Document): + pass + + +@frappe.whitelist() +def get_student_attendance_records( + based_on, date=None, student_group=None, course_schedule=None +): + student_list = [] + student_attendance_list = [] + + if based_on == "Course Schedule": + student_group = frappe.db.get_value( + "Course Schedule", course_schedule, "student_group" + ) + if student_group: + student_list = frappe.get_all( + "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_all( + "Student Group Student", + fields=["student", "student_name", "group_roll_number"], + filters={"parent": student_group, "active": 1}, + order_by="group_roll_number", + ) + + StudentAttendance = frappe.qb.DocType("Student Attendance") + + if course_schedule: + student_attendance_list = ( + frappe.qb.from_(StudentAttendance) + .select(StudentAttendance.student, StudentAttendance.status) + .where((StudentAttendance.course_schedule == course_schedule)) + ).run(as_dict=True) + else: + student_attendance_list = ( + frappe.qb.from_(StudentAttendance) + .select(StudentAttendance.student, StudentAttendance.status) + .where( + (StudentAttendance.student_group == student_group) + & (StudentAttendance.date == date) + & ( + (StudentAttendance.course_schedule == "") + | (StudentAttendance.course_schedule.isnull()) + ) + ) + ).run(as_dict=True) + + for attendance in student_attendance_list: + for student in student_list: + if student.student == attendance.student: + student.status = attendance.status + + return student_list diff --git a/education/education/doctype/student_attendance_tool/test_student_attendance_tool.py b/education/education/doctype/student_attendance_tool/test_student_attendance_tool.py new file mode 100644 index 0000000..c501f8a --- /dev/null +++ b/education/education/doctype/student_attendance_tool/test_student_attendance_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestStudentAttendanceTool(unittest.TestCase): + pass diff --git a/education/education/doctype/student_batch_name/__init__.py b/education/education/doctype/student_batch_name/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_batch_name/student_batch_name.js b/education/education/doctype/student_batch_name/student_batch_name.js new file mode 100644 index 0000000..4b206ad --- /dev/null +++ b/education/education/doctype/student_batch_name/student_batch_name.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Batch Name', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/student_batch_name/student_batch_name.json b/education/education/doctype/student_batch_name/student_batch_name.json new file mode 100644 index 0000000..abb6436 --- /dev/null +++ b/education/education/doctype/student_batch_name/student_batch_name.json @@ -0,0 +1,94 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:batch_name", + "beta": 0, + "creation": "2016-11-17 18:45:57.965091", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "batch_name", + "fieldtype": "Data", + "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": "Batch Name", + "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, + "unique": 0 + } + ], + "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": "2017-11-10 19:08:17.980349", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Batch Name", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_batch_name/student_batch_name.py b/education/education/doctype/student_batch_name/student_batch_name.py new file mode 100644 index 0000000..05d6aed --- /dev/null +++ b/education/education/doctype/student_batch_name/student_batch_name.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentBatchName(Document): + pass diff --git a/education/education/doctype/student_batch_name/test_records.json b/education/education/doctype/student_batch_name/test_records.json new file mode 100644 index 0000000..bf365c6 --- /dev/null +++ b/education/education/doctype/student_batch_name/test_records.json @@ -0,0 +1,8 @@ +[ + { + "batch_name": "_Batch 1" + }, + { + "batch_name": "_Batch 2" + } +] \ No newline at end of file diff --git a/education/education/doctype/student_batch_name/test_student_batch_name.py b/education/education/doctype/student_batch_name/test_student_batch_name.py new file mode 100644 index 0000000..394807a --- /dev/null +++ b/education/education/doctype/student_batch_name/test_student_batch_name.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Batch Name') + + +class TestStudentBatchName(unittest.TestCase): + pass diff --git a/education/education/doctype/student_category/__init__.py b/education/education/doctype/student_category/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_category/student_category.js b/education/education/doctype/student_category/student_category.js new file mode 100644 index 0000000..82dd656 --- /dev/null +++ b/education/education/doctype/student_category/student_category.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Category', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/student_category/student_category.json b/education/education/doctype/student_category/student_category.json new file mode 100644 index 0000000..14820f8 --- /dev/null +++ b/education/education/doctype/student_category/student_category.json @@ -0,0 +1,93 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 1, + "autoname": "field:category", + "beta": 0, + "creation": "2016-09-05 06:28:33.679415", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "category", + "fieldtype": "Data", + "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": "Category", + "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, + "unique": 0 + } + ], + "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": "2020-09-29 10:58:45.783401", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Category", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 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, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0 +} diff --git a/education/education/doctype/student_category/student_category.py b/education/education/doctype/student_category/student_category.py new file mode 100644 index 0000000..8f79859 --- /dev/null +++ b/education/education/doctype/student_category/student_category.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentCategory(Document): + pass diff --git a/education/education/doctype/student_category/student_category_dashboard.py b/education/education/doctype/student_category/student_category_dashboard.py new file mode 100644 index 0000000..6e5105f --- /dev/null +++ b/education/education/doctype/student_category/student_category_dashboard.py @@ -0,0 +1,10 @@ +from frappe import _ + + +def get_data(): + return { + "fieldname": "student_category", + "transactions": [ + {"label": _("Fee"), "items": ["Fee Structure", "Fee Schedule", "Fees"]} + ], + } diff --git a/education/education/doctype/student_category/test_student_category.py b/education/education/doctype/student_category/test_student_category.py new file mode 100644 index 0000000..e0d9d9c --- /dev/null +++ b/education/education/doctype/student_category/test_student_category.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Category') + + +class TestStudentCategory(unittest.TestCase): + pass diff --git a/education/education/doctype/student_group/__init__.py b/education/education/doctype/student_group/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_group/student_group.js b/education/education/doctype/student_group/student_group.js new file mode 100644 index 0000000..ea15bce --- /dev/null +++ b/education/education/doctype/student_group/student_group.js @@ -0,0 +1,145 @@ +cur_frm.add_fetch('student', 'title', 'student_name'); + +frappe.ui.form.on('Student Group', { + onload: function(frm) { + frm.set_query('academic_term', function() { + return { + filters: { + 'academic_year': (frm.doc.academic_year) + } + }; + }); + if (!frm.__islocal) { + frm.set_query('student', 'students', function() { + return{ + query: 'education.education.doctype.student_group.student_group.fetch_students', + filters: { + 'academic_year': frm.doc.academic_year, + 'group_based_on': frm.doc.group_based_on, + 'academic_term': frm.doc.academic_term, + 'program': frm.doc.program, + 'batch': frm.doc.batch, + 'student_category': frm.doc.student_category, + 'course': frm.doc.course, + 'student_group': frm.doc.name + } + } + }); + } + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + + frm.add_custom_button(__('Add Guardians to Email Group'), function() { + frappe.call({ + method: 'education.education.api.update_email_group', + args: { + 'doctype': 'Student Group', + 'name': frm.doc.name + } + }); + }, __('Actions')); + + frm.add_custom_button(__('Student Attendance Tool'), function() { + frappe.route_options = { + based_on: 'Student Group', + student_group: frm.doc.name + } + frappe.set_route('Form', 'Student Attendance Tool', 'Student Attendance Tool'); + }, __('Tools')); + + frm.add_custom_button(__('Course Scheduling Tool'), function() { + frappe.route_options = { + student_group: frm.doc.name + } + frappe.set_route('Form', 'Course Scheduling Tool', 'Course Scheduling Tool'); + }, __('Tools')); + + frm.add_custom_button(__('Newsletter'), function() { + frappe.route_options = { + 'Newsletter Email Group.email_group': frm.doc.name + } + frappe.set_route('List', 'Newsletter'); + }, __('View')); + + } + }, + + group_based_on: function(frm) { + if (frm.doc.group_based_on == 'Batch') { + frm.doc.course = null; + frm.set_df_property('program', 'reqd', 1); + frm.set_df_property('course', 'reqd', 0); + } + else if (frm.doc.group_based_on == 'Course') { + frm.set_df_property('program', 'reqd', 0); + frm.set_df_property('course', 'reqd', 1); + } + else if (frm.doc.group_based_on == 'Activity') { + frm.set_df_property('program', 'reqd', 0); + frm.set_df_property('course', 'reqd', 0); + } + }, + + get_students: function(frm) { + if (frm.doc.group_based_on == 'Batch' || frm.doc.group_based_on == 'Course') { + var student_list = []; + var max_roll_no = 0; + $.each(frm.doc.students, function(_i,d) { + student_list.push(d.student); + if (d.group_roll_number>max_roll_no) { + max_roll_no = d.group_roll_number; + } + }); + + if (frm.doc.academic_year) { + frappe.call({ + method: 'education.education.doctype.student_group.student_group.get_students', + args: { + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term, + 'group_based_on': frm.doc.group_based_on, + 'program': frm.doc.program, + 'batch' : frm.doc.batch, + 'student_category' : frm.doc.student_category, + 'course': frm.doc.course + }, + callback: function(r) { + if (r.message) { + $.each(r.message, function(i, d) { + if(!in_list(student_list, d.student)) { + var s = frm.add_child('students'); + s.student = d.student; + s.student_name = d.student_name; + if (d.active === 0) { + s.active = 0; + } + s.group_roll_number = ++max_roll_no; + } + }); + refresh_field('students'); + frm.save(); + } else { + frappe.msgprint(__('Student Group is already updated.')) + } + } + }) + } + } else { + frappe.msgprint(__('Select students manually for the Activity based Group')); + } + } +}); + +frappe.ui.form.on('Student Group Instructor', { + instructors_add: function(frm){ + frm.fields_dict['instructors'].grid.get_field('instructor').get_query = function(doc){ + let instructor_list = []; + $.each(doc.instructors, function(idx, val){ + instructor_list.push(val.instructor); + }); + return { filters: [['Instructor', 'name', 'not in', instructor_list]] }; + }; + } +}); diff --git a/education/education/doctype/student_group/student_group.json b/education/education/doctype/student_group/student_group.json new file mode 100644 index 0000000..44b38b1 --- /dev/null +++ b/education/education/doctype/student_group/student_group.json @@ -0,0 +1,161 @@ +{ + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:student_group_name", + "creation": "2015-09-07 12:55:52.072792", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "academic_year", + "group_based_on", + "student_group_name", + "max_strength", + "column_break_3", + "academic_term", + "program", + "batch", + "student_category", + "course", + "disabled", + "section_break_6", + "get_students", + "students", + "section_break_12", + "instructors" + ], + "fields": [ + { + "fieldname": "academic_year", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Academic Year", + "options": "Academic Year", + "set_only_once": 1 + }, + { + "fieldname": "group_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Group Based on", + "options": "\nBatch\nCourse\nActivity", + "reqd": 1 + }, + { + "fieldname": "student_group_name", + "fieldtype": "Data", + "label": "Student Group Name", + "reqd": 1, + "unique": 1 + }, + { + "description": "Set 0 for no limit", + "fieldname": "max_strength", + "fieldtype": "Int", + "label": "Max Strength" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "academic_term", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Academic Term", + "options": "Academic Term" + }, + { + "fieldname": "program", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Program", + "options": "Program" + }, + { + "fieldname": "batch", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Batch", + "options": "Student Batch Name" + }, + { + "depends_on": "eval:doc.group_based_on == 'Course'", + "fieldname": "course", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Course", + "options": "Course" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Students" + }, + { + "fieldname": "get_students", + "fieldtype": "Button", + "label": "Get Students" + }, + { + "allow_on_submit": 1, + "fieldname": "students", + "fieldtype": "Table", + "label": "Students", + "options": "Student Group Student" + }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "label": "Instructors" + }, + { + "fieldname": "instructors", + "fieldtype": "Table", + "label": "Instructors", + "options": "Student Group Instructor" + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category" + } + ], + "modified": "2019-04-26 10:52:57.303951", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Group", + "owner": "Administrator", + "permissions": [ + { + "read": 1, + "role": "Instructor" + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "write": 1 + } + ], + "restrict_to_domain": "Education", + "search_fields": "program, batch, course", + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/education/education/doctype/student_group/student_group.py b/education/education/doctype/student_group/student_group.py new file mode 100644 index 0000000..6f24217 --- /dev/null +++ b/education/education/doctype/student_group/student_group.py @@ -0,0 +1,213 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cint + +from education.education.utils import validate_duplicate_student + + +class StudentGroup(Document): + def validate(self): + self.validate_mandatory_fields() + self.validate_strength() + self.validate_students() + self.validate_and_set_child_table_fields() + validate_duplicate_student(self.students) + + def validate_mandatory_fields(self): + if self.group_based_on == "Course" and not self.course: + frappe.throw(_("Please select Course")) + if self.group_based_on == "Course" and (not self.program and self.batch): + frappe.throw(_("Please select Program")) + if self.group_based_on == "Batch" and not self.program: + frappe.throw(_("Please select Program")) + + def validate_strength(self): + if cint(self.max_strength) < 0: + frappe.throw(_("""Max strength cannot be less than zero.""")) + if self.max_strength and len(self.students) > self.max_strength: + frappe.throw( + _("""Cannot enroll more than {0} students for this student group.""").format( + self.max_strength + ) + ) + + def validate_students(self): + program_enrollment = get_program_enrollment( + self.academic_year, + self.academic_term, + self.program, + self.batch, + self.student_category, + self.course, + ) + students = [d.student for d in program_enrollment] if program_enrollment else [] + for d in self.students: + if ( + not frappe.db.get_value("Student", d.student, "enabled") + and d.active + and not self.disabled + ): + frappe.throw( + _("{0} - {1} is inactive student").format(d.group_roll_number, d.student_name) + ) + + if ( + (self.group_based_on == "Batch") + and cint(frappe.defaults.get_defaults().validate_batch) + and d.student not in students + ): + frappe.throw( + _("{0} - {1} is not enrolled in the Batch {2}").format( + d.group_roll_number, d.student_name, self.batch + ) + ) + + if ( + (self.group_based_on == "Course") + and cint(frappe.defaults.get_defaults().validate_course) + and (d.student not in students) + ): + frappe.throw( + _("{0} - {1} is not enrolled in the Course {2}").format( + d.group_roll_number, d.student_name, self.course + ) + ) + + def validate_and_set_child_table_fields(self): + roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number] + max_roll_no = max(roll_numbers) if roll_numbers else 0 + roll_no_list = [] + for d in self.students: + if not d.student_name: + d.student_name = frappe.db.get_value("Student", d.student, "title") + if not d.group_roll_number: + max_roll_no += 1 + d.group_roll_number = max_roll_no + if d.group_roll_number in roll_no_list: + frappe.throw(_("Duplicate roll number for student {0}").format(d.student_name)) + else: + roll_no_list.append(d.group_roll_number) + + +@frappe.whitelist() +def get_students( + academic_year, + group_based_on, + academic_term=None, + program=None, + batch=None, + student_category=None, + course=None, +): + enrolled_students = get_program_enrollment( + academic_year, academic_term, program, batch, student_category, course + ) + + if enrolled_students: + student_list = [] + for s in enrolled_students: + if frappe.db.get_value("Student", s.student, "enabled"): + s.update({"active": 1}) + else: + s.update({"active": 0}) + student_list.append(s) + return student_list + else: + frappe.msgprint(_("No students found")) + return [] + + +def get_program_enrollment( + academic_year, + academic_term=None, + program=None, + batch=None, + student_category=None, + course=None, +): + + condition1 = " " + condition2 = " " + if academic_term: + condition1 += " and pe.academic_term = %(academic_term)s" + if program: + condition1 += " and pe.program = %(program)s" + if batch: + condition1 += " and pe.student_batch_name = %(batch)s" + if student_category: + condition1 += " and pe.student_category = %(student_category)s" + if course: + condition1 += " and pe.name = pec.parent and pec.course = %(course)s" + condition2 = ", `tabProgram Enrollment Course` pec" + + return frappe.db.sql( + """ + select + pe.student, pe.student_name + from + `tabProgram Enrollment` pe {condition2} + where + pe.academic_year = %(academic_year)s {condition1} + order by + pe.student_name asc + """.format( + condition1=condition1, condition2=condition2 + ), + ( + { + "academic_year": academic_year, + "academic_term": academic_term, + "program": program, + "batch": batch, + "student_category": student_category, + "course": course, + } + ), + as_dict=1, + ) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def fetch_students(doctype, txt, searchfield, start, page_len, filters): + if filters.get("group_based_on") != "Activity": + enrolled_students = get_program_enrollment( + filters.get("academic_year"), + filters.get("academic_term"), + filters.get("program"), + filters.get("batch"), + filters.get("student_category"), + ) + student_group_student = frappe.db.sql_list( + """select student from `tabStudent Group Student` where parent=%s""", + (filters.get("student_group")), + ) + students = ( + [d.student for d in enrolled_students if d.student not in student_group_student] + if enrolled_students + else [""] + ) or [""] + return frappe.db.sql( + """select name, title from tabStudent + where name in ({0}) and (`{1}` LIKE %s or title LIKE %s) + order by idx desc, name + limit %s, %s""".format( + ", ".join(["%s"] * len(students)), searchfield + ), + tuple(students + ["%%%s%%" % txt, "%%%s%%" % txt, start, page_len]), + ) + else: + return frappe.db.sql( + """select name, title from tabStudent + where `{0}` LIKE %s or title LIKE %s + order by idx desc, name + limit %s, %s""".format( + searchfield + ), + tuple(["%%%s%%" % txt, "%%%s%%" % txt, start, page_len]), + ) diff --git a/education/education/doctype/student_group/student_group_dashboard.py b/education/education/doctype/student_group/student_group_dashboard.py new file mode 100644 index 0000000..aa0b1e2 --- /dev/null +++ b/education/education/doctype/student_group/student_group_dashboard.py @@ -0,0 +1,14 @@ +# Copyright (c) 2020, InfluxERP +# License: GNU General Public License v3. See license.txt + +from frappe import _ + + +def get_data(): + return { + "fieldname": "student_group", + "transactions": [ + {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}, + {"label": _("Course"), "items": ["Course Schedule"]}, + ], + } diff --git a/education/education/doctype/student_group/test_records.json b/education/education/doctype/student_group/test_records.json new file mode 100644 index 0000000..4c4e042 --- /dev/null +++ b/education/education/doctype/student_group/test_records.json @@ -0,0 +1,34 @@ +[ + { + "student_group_name": "Batch-_TP1-_Batch 1-2014-2015 (_Test Academic Term)", + "group_based_on": "Batch", + "program": "_TP1", + "batch": "_Batch 1", + "academic_year": "2014-2015", + "academic_term": "2014-2015 (_Test Academic Term)", + "max_strength": 0 + }, + { + "student_group_name": "Course-TC101-2014-2015 (_Test Academic Term)", + "group_based_on": "Course", + "course": "TC101", + "academic_year": "2014-2015", + "academic_term": "2014-2015 (_Test Academic Term)", + "max_strength": 0 + }, + { + "student_group_name": "Course-TC102-2014-2015 (_Test Academic Term)", + "group_based_on": "Course", + "course": "TC102", + "academic_year": "2014-2015", + "academic_term": "2014-2015 (_Test Academic Term)", + "max_strength": 0 + }, + { + "student_group_name": "Activity-2014-2015 (_Test Academic Term)", + "group_based_on": "Activity", + "academic_year": "2014-2015", + "academic_term": "2014-2015 (_Test Academic Term)", + "max_strength": 0 + } +] \ No newline at end of file diff --git a/education/education/doctype/student_group/test_student_group.py b/education/education/doctype/student_group/test_student_group.py new file mode 100644 index 0000000..bfde97b --- /dev/null +++ b/education/education/doctype/student_group/test_student_group.py @@ -0,0 +1,52 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe + +import education.education + + +def get_random_group(): + doc = frappe.get_doc( + { + "doctype": "Student Group", + "student_group_name": "_Test Student Group-" + frappe.generate_hash(length=5), + "group_based_on": "Activity", + } + ).insert() + + student_list = frappe.get_all("Student", limit=5) + + doc.extend("students", [{"student": d.name, "active": 1} for d in student_list]) + doc.save() + + return doc + + +class TestStudentGroup(unittest.TestCase): + def test_student_roll_no(self): + doc = get_random_group() + self.assertEqual(max([d.group_roll_number for d in doc.students]), len(doc.students)) + + def test_in_group(self): + doc = get_random_group() + + last_student = doc.students[-1].student + + # remove last student + doc.students = doc.students[:-1] + doc.save() + + self.assertRaises( + education.education.StudentNotInGroupError, + education.education.validate_student_belongs_to_group, + last_student, + doc.name, + ) + + # safe, don't throw validation + education.education.validate_student_belongs_to_group( + doc.students[0].student, doc.name + ) diff --git a/education/education/doctype/student_group_creation_tool/__init__.py b/education/education/doctype/student_group_creation_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_group_creation_tool/student_group_creation_tool.js b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.js new file mode 100644 index 0000000..c189e27 --- /dev/null +++ b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.js @@ -0,0 +1,40 @@ +frappe.ui.form.on("Student Group Creation Tool", "refresh", function(frm) { + frm.disable_save(); + frm.page.set_primary_action(__("Create Student Groups"), function() { + frappe.call({ + method: "create_student_groups", + doc:frm.doc + }) + }); + frappe.realtime.on("student_group_creation_progress", function(data) { + if(data.progress) { + frappe.hide_msgprint(true); + frappe.show_progress(__("Creating student groups"), data.progress[0],data.progress[1]); + } + }); +}); + +frappe.ui.form.on("Student Group Creation Tool", "get_courses", function(frm) { + frm.set_value("courses",[]); + if (frm.doc.academic_year && frm.doc.program) { + frappe.call({ + method: "get_courses", + doc:frm.doc, + callback: function(r) { + if(r.message) { + frm.set_value("courses", r.message); + } + } + }) + } +}); + +frappe.ui.form.on("Student Group Creation Tool", "onload", function(frm){ + cur_frm.set_query("academic_term",function(){ + return{ + "filters":{ + "academic_year": (frm.doc.academic_year) + } + }; + }); +}); diff --git a/education/education/doctype/student_group_creation_tool/student_group_creation_tool.json b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.json new file mode 100644 index 0000000..fe7cbdb --- /dev/null +++ b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.json @@ -0,0 +1,309 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-01-04 14:45:36.576933", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year", + "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": "Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Leave blank if you make students groups per year", + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "get_courses", + "fieldtype": "Button", + "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": "Get Courses", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "program", + "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": "Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Leave unchecked if you don't want to consider batch while making course based groups. ", + "fieldname": "separate_groups", + "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": "Separate course based Group for every Batch", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "courses", + "fieldtype": "Table", + "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": "Courses", + "length": 0, + "no_copy": 0, + "options": "Student Group Creation Tool Course", + "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, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 1, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-12-27 09:35:30.211254", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Group Creation Tool", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Education Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "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", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_group_creation_tool/student_group_creation_tool.py b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.py new file mode 100644 index 0000000..bb1ca5d --- /dev/null +++ b/education/education/doctype/student_group_creation_tool/student_group_creation_tool.py @@ -0,0 +1,111 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ +from frappe.model.document import Document + +from education.education.doctype.student_group.student_group import \ + get_students + + +class StudentGroupCreationTool(Document): + @frappe.whitelist() + def get_courses(self): + group_list = [] + + batches = frappe.db.sql( + """select name as batch from `tabStudent Batch Name`""", as_dict=1 + ) + for batch in batches: + group_list.append({"group_based_on": "Batch", "batch": batch.batch}) + + courses = frappe.db.sql( + """select course, course_name from `tabProgram Course` where parent=%s""", + (self.program), + as_dict=1, + ) + if self.separate_groups: + from itertools import product + + course_list = product(courses, batches) + for course in course_list: + temp_dict = {} + temp_dict.update({"group_based_on": "Course"}) + temp_dict.update(course[0]) + temp_dict.update(course[1]) + group_list.append(temp_dict) + else: + for course in courses: + course.update({"group_based_on": "Course"}) + group_list.append(course) + + for group in group_list: + if group.get("group_based_on") == "Batch": + student_group_name = ( + self.program + + "/" + + group.get("batch") + + "/" + + (self.academic_term if self.academic_term else self.academic_year) + ) + group.update({"student_group_name": student_group_name}) + elif group.get("group_based_on") == "Course": + student_group_name = ( + group.get("course") + + "/" + + self.program + + ("/" + group.get("batch") if group.get("batch") else "") + + "/" + + (self.academic_term if self.academic_term else self.academic_year) + ) + group.update({"student_group_name": student_group_name}) + + return group_list + + @frappe.whitelist() + def create_student_groups(self): + if not self.courses: + frappe.throw(_("""No Student Groups created.""")) + + l = len(self.courses) + for d in self.courses: + if not d.student_group_name: + frappe.throw(_("Student Group Name is mandatory in row {0}").format(d.idx)) + + if d.group_based_on == "Course" and not d.course: + frappe.throw(_("Course is mandatory in row {0}").format(d.idx)) + + if d.group_based_on == "Batch" and not d.batch: + frappe.throw(_("Batch is mandatory in row {0}").format(d.idx)) + + frappe.publish_realtime( + "student_group_creation_progress", + {"progress": [d.idx, l]}, + user=frappe.session.user, + ) + + student_group = frappe.new_doc("Student Group") + student_group.student_group_name = d.student_group_name + student_group.group_based_on = d.group_based_on + student_group.program = self.program + student_group.course = d.course + student_group.batch = d.batch + student_group.max_strength = d.max_strength + student_group.academic_term = self.academic_term + student_group.academic_year = self.academic_year + student_list = get_students( + self.academic_year, + d.group_based_on, + self.academic_term, + self.program, + d.batch, + d.course, + ) + + for student in student_list: + student_group.append("students", student) + student_group.save() + + frappe.msgprint(_("{0} Student Groups created.").format(l)) diff --git a/education/education/doctype/student_group_creation_tool/test_student_group_creation_tool.py b/education/education/doctype/student_group_creation_tool/test_student_group_creation_tool.py new file mode 100644 index 0000000..b145af2 --- /dev/null +++ b/education/education/doctype/student_group_creation_tool/test_student_group_creation_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2017, InfluxERP +# See license.txt + +import unittest + + +class TestStudentGroupCreationTool(unittest.TestCase): + pass diff --git a/education/education/doctype/student_group_creation_tool_course/__init__.py b/education/education/doctype/student_group_creation_tool_course/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.json b/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.json new file mode 100644 index 0000000..9f691a1 --- /dev/null +++ b/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.json @@ -0,0 +1,272 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-01-04 15:03:57.940079", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "group_based_on", + "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": 0, + "label": "Group Based On", + "length": 0, + "no_copy": 0, + "options": "\nBatch\nCourse", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "course", + "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", + "length": 0, + "no_copy": 0, + "options": "Course", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "batch", + "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": "Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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_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, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_group_name", + "fieldtype": "Data", + "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": "Student Group Name", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "course.course_name", + "fieldname": "course_code", + "fieldtype": "Read Only", + "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": "Course Code", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "max_strength", + "fieldtype": "Int", + "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": "Max Strength", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:38:52.525155", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Group Creation Tool Course", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "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", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.py b/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.py new file mode 100644 index 0000000..d3e421e --- /dev/null +++ b/education/education/doctype/student_group_creation_tool_course/student_group_creation_tool_course.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentGroupCreationToolCourse(Document): + pass diff --git a/education/education/doctype/student_group_instructor/__init__.py b/education/education/doctype/student_group_instructor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_group_instructor/student_group_instructor.json b/education/education/doctype/student_group_instructor/student_group_instructor.json new file mode 100644 index 0000000..cb4e527 --- /dev/null +++ b/education/education/doctype/student_group_instructor/student_group_instructor.json @@ -0,0 +1,142 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-04-17 16:06:01.406768", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "instructor", + "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": "Instructor", + "length": 0, + "no_copy": 0, + "options": "Instructor", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "instructor.instructor_name", + "fieldname": "instructor_name", + "fieldtype": "Read Only", + "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": "Instructor 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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:39:02.413082", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Group Instructor", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_group_instructor/student_group_instructor.py b/education/education/doctype/student_group_instructor/student_group_instructor.py new file mode 100644 index 0000000..a04fa43 --- /dev/null +++ b/education/education/doctype/student_group_instructor/student_group_instructor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentGroupInstructor(Document): + pass diff --git a/education/education/doctype/student_group_student/__init__.py b/education/education/doctype/student_group_student/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_group_student/student_group_student.json b/education/education/doctype/student_group_student/student_group_student.json new file mode 100644 index 0000000..d55db34 --- /dev/null +++ b/education/education/doctype/student_group_student/student_group_student.json @@ -0,0 +1,204 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2015-09-11 15:14:58.501830", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "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": 0, + "in_list_view": 1, + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_name", + "fieldtype": "Data", + "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": "Student Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "group_roll_number", + "fieldtype": "Int", + "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": "Group Roll Number", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "active", + "fieldtype": "Check", + "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": "Active", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:38:22.896203", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Group Student", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "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", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_group_student/student_group_student.py b/education/education/doctype/student_group_student/student_group_student.py new file mode 100644 index 0000000..1e3c1e1 --- /dev/null +++ b/education/education/doctype/student_group_student/student_group_student.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentGroupStudent(Document): + pass diff --git a/education/education/doctype/student_guardian/__init__.py b/education/education/doctype/student_guardian/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_guardian/student_guardian.json b/education/education/doctype/student_guardian/student_guardian.json new file mode 100644 index 0000000..3f03a3d --- /dev/null +++ b/education/education/doctype/student_guardian/student_guardian.json @@ -0,0 +1,141 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-09-01 14:28:39.174471", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "guardian", + "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": "Guardian", + "length": 0, + "no_copy": 0, + "options": "Guardian", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "guardian_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Guardian Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "relation", + "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": 0, + "label": "Relation", + "length": 0, + "no_copy": 0, + "options": "\nMother\nFather\nOthers", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:38:14.211238", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Guardian", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_guardian/student_guardian.py b/education/education/doctype/student_guardian/student_guardian.py new file mode 100644 index 0000000..e14ca8f --- /dev/null +++ b/education/education/doctype/student_guardian/student_guardian.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentGuardian(Document): + pass diff --git a/education/education/doctype/student_language/__init__.py b/education/education/doctype/student_language/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_language/student_language.js b/education/education/doctype/student_language/student_language.js new file mode 100644 index 0000000..28d4892 --- /dev/null +++ b/education/education/doctype/student_language/student_language.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Language', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/student_language/student_language.json b/education/education/doctype/student_language/student_language.json new file mode 100644 index 0000000..fc53cd1 --- /dev/null +++ b/education/education/doctype/student_language/student_language.json @@ -0,0 +1,98 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:language_name", + "beta": 0, + "creation": "2017-02-21 01:55:00.366273", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "language_name", + "fieldtype": "Data", + "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": "Language Name", + "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": 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-11-04 03:37:34.712397", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Language", + "name_case": "", + "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, + "write": 1 + } + ], + "quick_entry": 1, + "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": "", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_language/student_language.py b/education/education/doctype/student_language/student_language.py new file mode 100644 index 0000000..e5322d4 --- /dev/null +++ b/education/education/doctype/student_language/student_language.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentLanguage(Document): + pass diff --git a/education/education/doctype/student_language/test_student_language.py b/education/education/doctype/student_language/test_student_language.py new file mode 100644 index 0000000..143e31d --- /dev/null +++ b/education/education/doctype/student_language/test_student_language.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Language') + + +class TestStudentLanguage(unittest.TestCase): + pass diff --git a/education/education/doctype/student_leave_application/__init__.py b/education/education/doctype/student_leave_application/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_leave_application/student_leave_application.js b/education/education/doctype/student_leave_application/student_leave_application.js new file mode 100644 index 0000000..37aef25 --- /dev/null +++ b/education/education/doctype/student_leave_application/student_leave_application.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Leave Application', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/student_leave_application/student_leave_application.json b/education/education/doctype/student_leave_application/student_leave_application.json new file mode 100644 index 0000000..31b3da2 --- /dev/null +++ b/education/education/doctype/student_leave_application/student_leave_application.json @@ -0,0 +1,165 @@ +{ + "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", + "total_leave_days", + "section_break_5", + "attendance_based_on", + "student_group", + "course_schedule", + "mark_as_present", + "column_break_11", + "reason", + "amended_from" + ], + "fields": [ + { + "fieldname": "student", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Student", + "options": "Student", + "reqd": 1 + }, + { + "fetch_from": "student.title", + "fieldname": "student_name", + "fieldtype": "Read Only", + "in_global_search": 1, + "label": "Student Name", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "From Date", + "reqd": 1 + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "To Date", + "reqd": 1 + }, + { + "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" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "reason", + "fieldtype": "Text", + "label": "Reason" + }, + { + "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" + }, + { + "fieldname": "total_leave_days", + "fieldtype": "Float", + "label": "Total Leave Days", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-09-21 18:10:24.440669", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Leave Application", + "owner": "Administrator", + "permissions": [ + { + "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, + "print": 1, + "read": 1, + "report": 1, + "role": "Academics User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "restrict_to_domain": "Education", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "student_name" +} \ No newline at end of file diff --git a/education/education/doctype/student_leave_application/student_leave_application.py b/education/education/doctype/student_leave_application/student_leave_application.py new file mode 100644 index 0000000..9cfdf71 --- /dev/null +++ b/education/education/doctype/student_leave_application/student_leave_application.py @@ -0,0 +1,139 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from datetime import timedelta + +import frappe +from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday +from frappe import _ +from frappe.model.document import Document +from frappe.utils import date_diff, flt, get_link_to_form, getdate + +from education.education.doctype.student_attendance.student_attendance import \ + get_holiday_list + + +class StudentLeaveApplication(Document): + def validate(self): + self.validate_holiday_list() + 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` + 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 + (%(from_date)s <= from_date and %(to_date)s >= to_date)) and + name != %(name)s and student = %(student)s and docstatus < 2 + """, + { + "from_date": self.from_date, + "to_date": self.to_date, + "student": self.student, + "name": self.name, + }, + 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, 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, "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) + + +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 diff --git a/education/education/doctype/student_leave_application/student_leave_application_dashboard.py b/education/education/doctype/student_leave_application/student_leave_application_dashboard.py new file mode 100644 index 0000000..6e4d180 --- /dev/null +++ b/education/education/doctype/student_leave_application/student_leave_application_dashboard.py @@ -0,0 +1,5 @@ +def get_data(): + return { + "fieldname": "leave_application", + "transactions": [{"items": ["Student Attendance"]}], + } diff --git a/education/education/doctype/student_leave_application/test_student_leave_application.py b/education/education/doctype/student_leave_application/test_student_leave_application.py new file mode 100644 index 0000000..f4b58ca --- /dev/null +++ b/education/education/doctype/student_leave_application/test_student_leave_application.py @@ -0,0 +1,139 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +import frappe +from erpnext import get_default_company +from frappe.utils import add_days, add_months, getdate + +from education.education.doctype.student.test_student import create_student +from education.education.doctype.student_group.test_student_group import \ + get_random_group + + +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() + 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 test_holiday(self): + today = getdate() + leave_application = create_leave_application( + from_date=today, to_date=add_days(today, 1), submit=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.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): + 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") + ) + + +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 diff --git a/education/education/doctype/student_log/__init__.py b/education/education/doctype/student_log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_log/student_log.js b/education/education/doctype/student_log/student_log.js new file mode 100644 index 0000000..530ddc6 --- /dev/null +++ b/education/education/doctype/student_log/student_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Log', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/student_log/student_log.json b/education/education/doctype/student_log/student_log.json new file mode 100644 index 0000000..cdd6aaa --- /dev/null +++ b/education/education/doctype/student_log/student_log.json @@ -0,0 +1,423 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "EDU-SLOG-.YYYY.-.#####", + "beta": 0, + "creation": "2016-07-29 03:27:22.451772", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "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": 0, + "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": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "type", + "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": "Type", + "length": 0, + "no_copy": 0, + "options": "General\nAcademic\nMedical\nAchievement", + "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_in_quick_entry": 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": 1, + "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": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "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, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year", + "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": "Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "program", + "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": "Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_batch", + "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": "Student Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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_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 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "log", + "fieldtype": "Text Editor", + "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": "Log", + "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 + } + ], + "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-08-21 16:15:47.027645", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Log", + "name_case": "", + "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, + "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": 1, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_log/student_log.py b/education/education/doctype/student_log/student_log.py new file mode 100644 index 0000000..14313ca --- /dev/null +++ b/education/education/doctype/student_log/student_log.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentLog(Document): + pass diff --git a/education/education/doctype/student_log/test_student_log.py b/education/education/doctype/student_log/test_student_log.py new file mode 100644 index 0000000..521dac8 --- /dev/null +++ b/education/education/doctype/student_log/test_student_log.py @@ -0,0 +1,10 @@ +# Copyright (c) 2015, InfluxERP +# See license.txt + +import unittest + +# test_records = frappe.get_test_records('Student Log') + + +class TestStudentLog(unittest.TestCase): + pass diff --git a/education/education/doctype/student_report_generation_tool/__init__.py b/education/education/doctype/student_report_generation_tool/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_report_generation_tool/student_report_generation_tool.html b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.html new file mode 100644 index 0000000..a9e84e6 --- /dev/null +++ b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.html @@ -0,0 +1,371 @@ + + + + +
+ +
+ + {% if letterhead and add_letterhead %} +
{{ letterhead }}
+ {% endif %} + +
+ +
+
+
+
+ +
+
+ {{ doc.students[0] }} +
+
+ +
+
+ +
+
+ {{ doc.student_name }} +
+
+ +
+
+ +
+
+ {{ doc.program }} +
+
+ +
+
+ +
+
+ {{ doc.student_batch }} +
+
+
+ +
+
+
+ +
+
+ {{ doc.academic_year }} +
+
+ + {% if doc.academic_term %} +
+
+ +
+
+ {{ doc.academic_term }} +
+
+ {% endif %} + +
+
+ +
+
+ {{ doc.assessment_group }} +
+
+
+
+ + +{% if doc.show_marks | int %} + {% set result_data = 'score' %} +{% else %} + {% set result_data = 'grade' %} +{% endif %} + + +{% for course in courses %} + +
+
+ +
+ + + + + + {% for assessment_group in assessment_groups %} + + {% endfor %} + + + + + {% for criteria in course_criteria[course] %} + + + {% for assessment_group in assessment_groups %} + {% if (assessment_result.get(course) and assessment_result.get(course).get(assessment_group) and assessment_result.get(course).get(assessment_group).get(criteria)) %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + + +
+
+
+ +
+
+ {{ course }} ({{ frappe.db.get_value("Course", course, "course_name") }}) +
+
+
{{ _("Assessment Criteria") }} {{ assessment_group }}
{{ criteria }} + {{ assessment_result.get(course).get(assessment_group).get(criteria).get(result_data) }} + {% if result_data == 'score' %} + ({{ assessment_result.get(course).get(assessment_group).get(criteria).get('maximum_score') }}) + {% endif %} +
+
+
+
+ +{% endfor %} + +
+ +
+
+

{{ _("Student Attendance")}}


+
+ Present {{ doc.attendance.get("Present") if doc.attendance.get("Present") != None else '0' }} days + out of {{ doc.attendance.get("Present") + doc.attendance.get("Absent") }} +
+
+ +
+

{{ _("Parents Teacher Meeting Attendance")}}


+
+ Present {{ doc.parents_attendance if doc.parents_attendance != None else '0' }} + out of {{ doc.parents_meeting if doc.parents_meeting != None else '0' }} +
+
+
+ +{% if doc.assessment_terms %} +
+
+

{{ doc.assessment_terms }}

+
+
+{% endif %} +
diff --git a/education/education/doctype/student_report_generation_tool/student_report_generation_tool.js b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.js new file mode 100644 index 0000000..7b07067 --- /dev/null +++ b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.js @@ -0,0 +1,66 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Student Report Generation Tool', { + onload: function(frm) { + frm.set_query("academic_term",function(){ + return{ + "filters":{ + "academic_year": frm.doc.academic_year + } + }; + }); + frm.set_query("assessment_group", function() { + return{ + filters: { + "is_group": 1 + } + }; + }); + }, + + refresh: function(frm) { + frm.disable_save(); + frm.page.clear_indicator(); + frm.page.set_primary_action(__('Print Report Card'), () => { + let url = "/api/method/education.education.doctype.student_report_generation_tool.student_report_generation_tool.preview_report_card"; + open_url_post(url, {"doc": frm.doc}, true); + }); + }, + + student: function(frm) { + if (frm.doc.student) { + frappe.call({ + method:"education.education.api.get_current_enrollment", + args: { + "student": frm.doc.student, + "academic_year": frm.doc.academic_year + }, + callback: function(r) { + if(r){ + $.each(r.message, function(i, d) { + if (frm.fields_dict.hasOwnProperty(i)) { + frm.set_value(i, d); + } + }); + } + } + }); + } + }, + + terms: function(frm) { + if(frm.doc.terms) { + return frappe.call({ + method: 'erpnext.setup.doctype.terms_and_conditions.terms_and_conditions.get_terms_and_conditions', + args: { + template_name: frm.doc.terms, + doc: frm.doc + }, + callback: function(r) { + frm.set_value("assessment_terms", r.message); + } + }); + } + } +}); diff --git a/education/education/doctype/student_report_generation_tool/student_report_generation_tool.json b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.json new file mode 100644 index 0000000..a328299 --- /dev/null +++ b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.json @@ -0,0 +1,618 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-01-15 15:36:32.830069", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "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": 0, + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_name", + "fieldtype": "Read Only", + "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": "Student Name", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "program", + "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": "Program", + "length": 0, + "no_copy": 0, + "options": "Program", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "student_batch", + "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": "Batch", + "length": 0, + "no_copy": 0, + "options": "Student Batch Name", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "include_all_assessment", + "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": "Include All Assessment Group", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "show_marks", + "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": "Show Marks", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "add_letterhead", + "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": "Add letterhead", + "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_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, + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_group", + "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": "Assessment Group", + "length": 0, + "no_copy": 0, + "options": "Assessment 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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_year", + "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": "Academic Year", + "length": 0, + "no_copy": 0, + "options": "Academic Year", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "academic_term", + "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": "Academic Term", + "length": 0, + "no_copy": 0, + "options": "Academic Term", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "add_letterhead", + "fieldname": "letter_head", + "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": "Letter Head", + "length": 0, + "no_copy": 0, + "options": "Letter Head", + "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_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, + "label": "Print Section", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "parents_meeting", + "fieldtype": "Data", + "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": "Total Parents Teacher Meeting", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "parents_attendance", + "fieldtype": "Data", + "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": "Attended by Parents", + "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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "terms", + "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": "Terms", + "length": 0, + "no_copy": 0, + "options": "Terms and Conditions", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "assessment_terms", + "fieldtype": "Text Editor", + "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": "Assessment Terms", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 1, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2018-11-04 03:38:42.970869", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Report Generation Tool", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "System Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_report_generation_tool/student_report_generation_tool.py b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.py new file mode 100644 index 0000000..e322b03 --- /dev/null +++ b/education/education/doctype/student_report_generation_tool/student_report_generation_tool.py @@ -0,0 +1,125 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils.pdf import get_pdf + +from education.education.report.course_wise_assessment_report.course_wise_assessment_report import ( + get_child_assessment_groups, get_formatted_result) + + +class StudentReportGenerationTool(Document): + pass + + +@frappe.whitelist() +def preview_report_card(doc): + doc = frappe._dict(json.loads(doc)) + doc.students = [doc.student] + if not (doc.student_name and doc.student_batch): + program_enrollment = frappe.get_all( + "Program Enrollment", + fields=["student_batch_name", "student_name"], + filters={ + "student": doc.student, + "docstatus": ("!=", 2), + "academic_year": doc.academic_year, + }, + ) + if program_enrollment: + doc.batch = program_enrollment[0].student_batch_name + doc.student_name = program_enrollment[0].student_name + + # get the assessment result of the selected student + values = get_formatted_result( + doc, get_course=True, get_all_assessment_groups=doc.include_all_assessment + ) + assessment_result = values.get("assessment_result").get(doc.student) + courses = values.get("course_dict") + course_criteria = get_courses_criteria(courses) + + # get the assessment group as per the user selection + if doc.include_all_assessment: + assessment_groups = get_child_assessment_groups(doc.assessment_group) + else: + assessment_groups = [doc.assessment_group] + + # get the attendance of the student for that peroid of time. + doc.attendance = get_attendance_count( + doc.students[0], doc.academic_year, doc.academic_term + ) + + template = "erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.html" + base_template_path = "frappe/www/printview.html" + + from frappe.www.printview import get_letter_head + + letterhead = get_letter_head( + frappe._dict({"letter_head": doc.letterhead}), not doc.add_letterhead + ) + + html = frappe.render_template( + template, + { + "doc": doc, + "assessment_result": assessment_result, + "courses": courses, + "assessment_groups": assessment_groups, + "course_criteria": course_criteria, + "letterhead": letterhead and letterhead.get("content", None), + "add_letterhead": doc.add_letterhead if doc.add_letterhead else 0, + }, + ) + final_template = frappe.render_template( + base_template_path, {"body": html, "title": "Report Card"} + ) + + frappe.response.filename = "Report Card " + doc.students[0] + ".pdf" + frappe.response.filecontent = get_pdf(final_template) + frappe.response.type = "download" + + +def get_courses_criteria(courses): + course_criteria = frappe._dict() + for course in courses: + course_criteria[course] = [ + d.assessment_criteria + for d in frappe.get_all( + "Course Assessment Criteria", + fields=["assessment_criteria"], + filters={"parent": course}, + ) + ] + return course_criteria + + +def get_attendance_count(student, academic_year, academic_term=None): + if academic_year: + from_date, to_date = frappe.db.get_value( + "Academic Year", academic_year, ["year_start_date", "year_end_date"] + ) + elif academic_term: + 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 and docstatus = 1 + and date between %s and %s group by status""", + (student, from_date, to_date), + ) + ) + if "Absent" not in attendance.keys(): + attendance["Absent"] = 0 + if "Present" not in attendance.keys(): + attendance["Present"] = 0 + return attendance + else: + frappe.throw(_("Provide the academic year and set the starting and ending date.")) diff --git a/education/education/doctype/student_report_generation_tool/test_student_report_generation_tool.py b/education/education/doctype/student_report_generation_tool/test_student_report_generation_tool.py new file mode 100644 index 0000000..f14afcf --- /dev/null +++ b/education/education/doctype/student_report_generation_tool/test_student_report_generation_tool.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestStudentReportGenerationTool(unittest.TestCase): + pass diff --git a/education/education/doctype/student_sibling/__init__.py b/education/education/doctype/student_sibling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_sibling/student_sibling.json b/education/education/doctype/student_sibling/student_sibling.json new file mode 100644 index 0000000..f67fe79 --- /dev/null +++ b/education/education/doctype/student_sibling/student_sibling.json @@ -0,0 +1,306 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-09-01 14:41:23.824083", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "studying_in_same_institute", + "fieldtype": "Select", + "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": "Studying in Same Institute", + "length": 0, + "no_copy": 0, + "options": "NO\nYES", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 3, + "fieldname": "full_name", + "fieldtype": "Data", + "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": "Full 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, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "gender", + "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": 0, + "label": "Gender", + "length": 0, + "no_copy": 0, + "options": "\nMale\nFemale", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_4", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.studying_in_same_institute == \"YES\"", + "fieldname": "student", + "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": "Student ID", + "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": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "depends_on": "eval:doc.studying_in_same_institute == \"NO\"", + "fieldname": "institution", + "fieldtype": "Data", + "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": "Institution", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "program", + "fieldtype": "Data", + "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": "Program", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "date_of_birth", + "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": "Date of Birth", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:37:25.881487", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Sibling", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_sibling/student_sibling.py b/education/education/doctype/student_sibling/student_sibling.py new file mode 100644 index 0000000..fdca88f --- /dev/null +++ b/education/education/doctype/student_sibling/student_sibling.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentSibling(Document): + pass diff --git a/education/education/doctype/student_siblings/__init__.py b/education/education/doctype/student_siblings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/student_siblings/student_siblings.json b/education/education/doctype/student_siblings/student_siblings.json new file mode 100644 index 0000000..9d91ad2 --- /dev/null +++ b/education/education/doctype/student_siblings/student_siblings.json @@ -0,0 +1,141 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-09-01 14:41:23.824083", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "name1", + "fieldtype": "Data", + "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": "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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gender", + "fieldtype": "Select", + "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": "Gender", + "length": 0, + "no_copy": 0, + "options": "\nMale\nFemale", + "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_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "date_of_birth", + "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 of Birth", + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2018-11-04 03:37:46.485218", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Siblings", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "restrict_to_domain": "Education", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/education/education/doctype/student_siblings/student_siblings.py b/education/education/doctype/student_siblings/student_siblings.py new file mode 100644 index 0000000..498adb7 --- /dev/null +++ b/education/education/doctype/student_siblings/student_siblings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2015, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class StudentSiblings(Document): + pass diff --git a/education/education/doctype/topic/__init__.py b/education/education/doctype/topic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/topic/test_topic.py b/education/education/doctype/topic/test_topic.py new file mode 100644 index 0000000..8874934 --- /dev/null +++ b/education/education/doctype/topic/test_topic.py @@ -0,0 +1,59 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + +import frappe + + +class TestTopic(unittest.TestCase): + def setUp(self): + make_topic_and_linked_content( + "_Test Topic 1", [{"type": "Article", "name": "_Test Article 1"}] + ) + + def test_get_contents(self): + topic = frappe.get_doc("Topic", "_Test Topic 1") + contents = topic.get_contents() + self.assertEqual(contents[0].doctype, "Article") + self.assertEqual(contents[0].name, "_Test Article 1") + frappe.db.rollback() + + +def make_topic(name): + try: + topic = frappe.get_doc("Topic", name) + except frappe.DoesNotExistError: + topic = frappe.get_doc( + { + "doctype": "Topic", + "topic_name": name, + "topic_code": name, + } + ).insert() + return topic.name + + +def make_topic_and_linked_content(topic_name, content_dict_list): + try: + topic = frappe.get_doc("Topic", topic_name) + except frappe.DoesNotExistError: + make_topic(topic_name) + topic = frappe.get_doc("Topic", topic_name) + content_list = [ + make_content(content["type"], content["name"]) for content in content_dict_list + ] + for content in content_list: + topic.append( + "topic_content", {"content": content.title, "content_type": content.doctype} + ) + topic.save() + return topic + + +def make_content(type, name): + try: + content = frappe.get_doc(type, name) + except frappe.DoesNotExistError: + content = frappe.get_doc({"doctype": type, "title": name}).insert() + return content diff --git a/education/education/doctype/topic/topic.js b/education/education/doctype/topic/topic.js new file mode 100644 index 0000000..9d3c07a --- /dev/null +++ b/education/education/doctype/topic/topic.js @@ -0,0 +1,55 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Topic', { + refresh: function(frm) { + if (!cur_frm.doc.__islocal) { + frm.add_custom_button(__('Add to Courses'), function() { + frm.trigger('add_topic_to_courses'); + }, __('Action')); + } + }, + + add_topic_to_courses: function(frm) { + get_courses_without_topic(frm.doc.name).then(r => { + if (r.message.length) { + frappe.prompt([ + { + fieldname: 'courses', + label: __('Courses'), + fieldtype: 'MultiSelectPills', + get_data: function() { + return r.message; + } + } + ], + function(data) { + frappe.call({ + method: 'education.education.doctype.topic.topic.add_topic_to_courses', + args: { + 'topic': frm.doc.name, + 'courses': data.courses + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + }, + freeze: true, + freeze_message: __('...Adding Topic to Courses') + }); + }, __('Add Topic to Courses'), __('Add')); + } else { + frappe.msgprint(__('This topic is already added to the existing courses')); + } + }); + } +}); + +let get_courses_without_topic = function(topic) { + return frappe.call({ + type: 'GET', + method: 'education.education.doctype.topic.topic.get_courses_without_topic', + args: {'topic': topic} + }); +}; diff --git a/education/education/doctype/topic/topic.json b/education/education/doctype/topic/topic.json new file mode 100644 index 0000000..305458b --- /dev/null +++ b/education/education/doctype/topic/topic.json @@ -0,0 +1,90 @@ +{ + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:topic_name", + "creation": "2018-12-12 11:37:39.917760", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "topic_name", + "topic_content", + "description", + "hero_image" + ], + "fields": [ + { + "fieldname": "topic_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "topic_content", + "fieldtype": "Table", + "label": "Topic Content", + "options": "Topic Content" + }, + { + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Hero Image" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + } + ], + "image_field": "hero_image", + "modified": "2019-06-12 12:34:49.911300", + "modified_by": "Administrator", + "module": "Education", + "name": "Topic", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Instructor", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/topic/topic.py b/education/education/doctype/topic/topic.py new file mode 100644 index 0000000..06ceb39 --- /dev/null +++ b/education/education/doctype/topic/topic.py @@ -0,0 +1,76 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +import json + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class Topic(Document): + def get_contents(self): + try: + topic_content_list = self.topic_content + content_data = [ + frappe.get_doc(topic_content.content_type, topic_content.content) + for topic_content in topic_content_list + ] + except Exception as e: + frappe.log_error(frappe.get_traceback()) + return None + return content_data + + +@frappe.whitelist() +def get_courses_without_topic(topic): + data = [] + for entry in frappe.db.get_all("Course"): + course = frappe.get_doc("Course", entry.name) + topics = [t.topic for t in course.topics] + if not topics or topic not in topics: + data.append(course.name) + return data + + +@frappe.whitelist() +def add_topic_to_courses(topic, courses, mandatory=False): + courses = json.loads(courses) + for entry in courses: + course = frappe.get_doc("Course", entry) + course.append("topics", {"topic": topic, "topic_name": topic}) + course.flags.ignore_mandatory = True + course.save() + frappe.db.commit() + frappe.msgprint( + _("Topic {0} has been added to all the selected courses successfully.").format( + frappe.bold(topic) + ), + title=_("Courses updated"), + indicator="green", + ) + + +@frappe.whitelist() +def add_content_to_topics(content_type, content, topics): + topics = json.loads(topics) + for entry in topics: + topic = frappe.get_doc("Topic", entry) + topic.append( + "topic_content", + { + "content_type": content_type, + "content": content, + }, + ) + topic.flags.ignore_mandatory = True + topic.save() + frappe.db.commit() + frappe.msgprint( + _("{0} {1} has been added to all the selected topics successfully.").format( + content_type, frappe.bold(content) + ), + title=_("Topics updated"), + indicator="green", + ) diff --git a/education/education/doctype/topic_content/__init__.py b/education/education/doctype/topic_content/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/doctype/topic_content/test_topic_content.py b/education/education/doctype/topic_content/test_topic_content.py new file mode 100644 index 0000000..04a7c51 --- /dev/null +++ b/education/education/doctype/topic_content/test_topic_content.py @@ -0,0 +1,8 @@ +# Copyright (c) 2018, InfluxERP +# See license.txt + +import unittest + + +class TestTopicContent(unittest.TestCase): + pass diff --git a/education/education/doctype/topic_content/topic_content.js b/education/education/doctype/topic_content/topic_content.js new file mode 100644 index 0000000..4bf8c16 --- /dev/null +++ b/education/education/doctype/topic_content/topic_content.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, InfluxERP +// For license information, please see license.txt + +frappe.ui.form.on('Topic Content', { + refresh: function(frm) { + + } +}); diff --git a/education/education/doctype/topic_content/topic_content.json b/education/education/doctype/topic_content/topic_content.json new file mode 100644 index 0000000..444fd1d --- /dev/null +++ b/education/education/doctype/topic_content/topic_content.json @@ -0,0 +1,44 @@ +{ + "creation": "2018-12-12 11:42:57.987434", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "content_type", + "column_break_2", + "content" + ], + "fields": [ + { + "fieldname": "content_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Content Type", + "options": "\nArticle\nVideo\nQuiz", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "content", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Content", + "options": "content_type", + "reqd": 1 + } + ], + "istable": 1, + "modified": "2019-05-14 11:12:49.153771", + "modified_by": "Administrator", + "module": "Education", + "name": "Topic Content", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/education/education/doctype/topic_content/topic_content.py b/education/education/doctype/topic_content/topic_content.py new file mode 100644 index 0000000..074360f --- /dev/null +++ b/education/education/doctype/topic_content/topic_content.py @@ -0,0 +1,9 @@ +# Copyright (c) 2018, InfluxERP +# For license information, please see license.txt + + +from frappe.model.document import Document + + +class TopicContent(Document): + pass diff --git a/education/education/report/__init__.py b/education/education/report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/absent_student_report/__init__.py b/education/education/report/absent_student_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/absent_student_report/absent_student_report.js b/education/education/report/absent_student_report/absent_student_report.js new file mode 100644 index 0000000..979ed31 --- /dev/null +++ b/education/education/report/absent_student_report/absent_student_report.js @@ -0,0 +1,15 @@ +// Copyright (c) 2015, InfluxERP +// License: GNU General Public License v3. See license.txt + + +frappe.query_reports["Absent Student Report"] = { + "filters": [ + { + "fieldname":"date", + "label": __("Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1 + } + ] +} diff --git a/education/education/report/absent_student_report/absent_student_report.json b/education/education/report/absent_student_report/absent_student_report.json new file mode 100644 index 0000000..92ad860 --- /dev/null +++ b/education/education/report/absent_student_report/absent_student_report.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "creation": "2013-05-13 14:04:03", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 3, + "is_standard": "Yes", + "modified": "2020-06-24 17:16:40.251116", + "modified_by": "Administrator", + "module": "Education", + "name": "Absent Student Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Student Attendance", + "report_name": "Absent Student Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Academics User" + } + ] +} \ No newline at end of file diff --git a/education/education/report/absent_student_report/absent_student_report.py b/education/education/report/absent_student_report/absent_student_report.py new file mode 100644 index 0000000..bdeed66 --- /dev/null +++ b/education/education/report/absent_student_report/absent_student_report.py @@ -0,0 +1,137 @@ +# Copyright (c) 2015, InfluxERP +# License: GNU General Public License v3. See license.txt + + +import frappe +from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday +from frappe import _, msgprint +from frappe.utils import formatdate + +from education.education.doctype.student_attendance.student_attendance import \ + get_holiday_list + + +def execute(filters=None): + if not filters: + filters = {} + + if not filters.get("date"): + msgprint(_("Please select date"), raise_exception=1) + + 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: + student_list = [d["student"] for d in absent_students] + transportation_details = get_transportation_details(date, student_list) + + data = [] + for student in absent_students: + 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", + _("Student Group") + "::180", + _("Student Email Address") + "::180", + _("Student Mobile No.") + "::150", + _("Mode of Transportation") + "::150", + _("Vehicle/Bus Number") + "::150", + ] + 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 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 = [] + 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): + academic_year = frappe.get_all( + "Academic Year", + filters=[["year_start_date", "<=", date], ["year_end_date", ">=", date]], + ) + if academic_year: + academic_year = academic_year[0].name + elif frappe.defaults.get_defaults().academic_year: + academic_year = frappe.defaults.get_defaults().academic_year + else: + return {} + + transportation_details = frappe.get_all( + "Program Enrollment", + fields=["student", "mode_of_transportation", "vehicle_no"], + filters={ + "student": ("in", student_list), + "academic_year": academic_year, + "docstatus": ("not in", ["2"]), + }, + ) + transportation_map = {} + for d in transportation_details: + transportation_map[d.student] = [d.mode_of_transportation, d.vehicle_no] + return transportation_map diff --git a/education/education/report/assessment_plan_status/__init__.py b/education/education/report/assessment_plan_status/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/assessment_plan_status/assessment_plan_status.js b/education/education/report/assessment_plan_status/assessment_plan_status.js new file mode 100644 index 0000000..9ad7b37 --- /dev/null +++ b/education/education/report/assessment_plan_status/assessment_plan_status.js @@ -0,0 +1,28 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Assessment Plan Status"] = { + "filters": [ + { + "fieldname":"assessment_group", + "label": __("Assessment Group"), + "fieldtype": "Link", + "options": "Assessment Group", + "get_query": function() { + return{ + filters: { + 'is_group': 0 + } + }; + } + }, + { + "fieldname":"schedule_date", + "label": __("Scheduled Upto"), + "fieldtype": "Date", + "options": "" + } + + ] +} diff --git a/education/education/report/assessment_plan_status/assessment_plan_status.json b/education/education/report/assessment_plan_status/assessment_plan_status.json new file mode 100644 index 0000000..cbca648 --- /dev/null +++ b/education/education/report/assessment_plan_status/assessment_plan_status.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "creation": "2017-11-09 15:07:30.404428", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-06-24 17:16:02.027410", + "modified_by": "Administrator", + "module": "Education", + "name": "Assessment Plan Status", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Assessment Plan", + "report_name": "Assessment Plan Status", + "report_type": "Script Report", + "roles": [ + { + "role": "Academics User" + } + ] +} \ No newline at end of file diff --git a/education/education/report/assessment_plan_status/assessment_plan_status.py b/education/education/report/assessment_plan_status/assessment_plan_status.py new file mode 100644 index 0000000..b4d3543 --- /dev/null +++ b/education/education/report/assessment_plan_status/assessment_plan_status.py @@ -0,0 +1,200 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +from itertools import groupby + +import frappe +from frappe import _ +from frappe.utils import cint + +DOCSTATUS = { + 0: "saved", + 1: "submitted", +} + + +def execute(filters=None): + columns, data = [], [] + + args = frappe._dict() + args["assessment_group"] = filters.get("assessment_group") + args["schedule_date"] = filters.get("schedule_date") + + columns = get_column() + + data, chart = get_assessment_data(args) + + return columns, data, None, chart + + +def get_assessment_data(args=None): + + # [total, saved, submitted, remaining] + chart_data = [0, 0, 0, 0] + + condition = "" + if args["assessment_group"]: + condition += "and assessment_group = %(assessment_group)s" + if args["schedule_date"]: + condition += "and schedule_date <= %(schedule_date)s" + + assessment_plan = frappe.db.sql( + """ + SELECT + ap.name as assessment_plan, + ap.assessment_name, + ap.student_group, + ap.schedule_date, + (select count(*) from `tabStudent Group Student` sgs where sgs.parent=ap.student_group) + as student_group_strength + FROM + `tabAssessment Plan` ap + WHERE + ap.docstatus = 1 {condition} + ORDER BY + ap.modified desc + """.format( + condition=condition + ), + (args), + as_dict=1, + ) + + assessment_plan_list = ( + [d.assessment_plan for d in assessment_plan] if assessment_plan else [""] + ) + assessment_result = get_assessment_result(assessment_plan_list) + + for d in assessment_plan: + + assessment_plan_details = assessment_result.get(d.assessment_plan) + assessment_plan_details = ( + frappe._dict() + if not assessment_plan_details + else frappe._dict(assessment_plan_details) + ) + if "saved" not in assessment_plan_details: + assessment_plan_details.update({"saved": 0}) + if "submitted" not in assessment_plan_details: + assessment_plan_details.update({"submitted": 0}) + + # remaining students whose marks not entered + remaining_students = ( + cint(d.student_group_strength) + - cint(assessment_plan_details.saved) + - cint(assessment_plan_details.submitted) + ) + assessment_plan_details.update({"remaining": remaining_students}) + d.update(assessment_plan_details) + + chart_data[0] += cint(d.student_group_strength) + chart_data[1] += assessment_plan_details.saved + chart_data[2] += assessment_plan_details.submitted + chart_data[3] += assessment_plan_details.remaining + + chart = get_chart(chart_data[1:]) + + return assessment_plan, chart + + +def get_assessment_result(assessment_plan_list): + assessment_result_dict = frappe._dict() + + assessment_result = frappe.db.sql( + """ + SELECT + assessment_plan, docstatus, count(*) as count + FROM + `tabAssessment Result` + WHERE + assessment_plan in (%s) + GROUP BY + assessment_plan, docstatus + ORDER BY + assessment_plan + """ + % ", ".join(["%s"] * len(assessment_plan_list)), + tuple(assessment_plan_list), + as_dict=1, + ) + + for key, group in groupby(assessment_result, lambda ap: ap["assessment_plan"]): + tmp = {} + for d in group: + if d.docstatus in [0, 1]: + tmp.update({DOCSTATUS[d.docstatus]: d.count}) + assessment_result_dict[key] = tmp + + return assessment_result_dict + + +def get_chart(chart_data): + return { + "data": { + "labels": ["Saved", "Submitted", "Remaining"], + "datasets": [{"values": chart_data}], + }, + "type": "percentage", + } + + +def get_column(): + return [ + { + "fieldname": "assessment_plan", + "label": _("Assessment Plan"), + "fieldtype": "Link", + "options": "Assessment Plan", + "width": 120, + }, + { + "fieldname": "assessment_name", + "label": _("Assessment Plan Name"), + "fieldtype": "Data", + "options": "", + "width": 200, + }, + { + "fieldname": "schedule_date", + "label": _("Schedule Date"), + "fieldtype": "Date", + "options": "", + "width": 100, + }, + { + "fieldname": "student_group", + "label": _("Student Group"), + "fieldtype": "Link", + "options": "Student Group", + "width": 200, + }, + { + "fieldname": "student_group_strength", + "label": _("Total Student"), + "fieldtype": "Data", + "options": "", + "width": 100, + }, + { + "fieldname": "submitted", + "label": _("Submitted"), + "fieldtype": "Data", + "options": "", + "width": 100, + }, + { + "fieldname": "saved", + "label": _("Saved"), + "fieldtype": "Data", + "options": "", + "width": 100, + }, + { + "fieldname": "remaining", + "label": _("Remaining"), + "fieldtype": "Data", + "options": "", + "width": 100, + }, + ] diff --git a/education/education/report/course_wise_assessment_report/__init__.py b/education/education/report/course_wise_assessment_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/course_wise_assessment_report/course_wise_assessment_report.html b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.html new file mode 100644 index 0000000..33e725f --- /dev/null +++ b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.html @@ -0,0 +1,50 @@ +{% + var letterhead = filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) || frappe.defaults.get_default("letter_head"); + var report_columns = report.get_columns_for_print(); +%} +{% if(letterhead) { %} +
+ {%= frappe.boot.letter_heads[letterhead].header %} +
+{% } %} +

{%= __("Assessment Report") %}

+
+
{%= __("Academic Year: ") %} {%= filters.academic_year %}
+{% if (filters.academic_term){ %} +
{%= __("Academic Term: ") %} {%= filters.academic_term %}
+{% } %} +
{%= __("Course Code: ") %} {%= filters.course %}
+
{%= __("Assessment Group: ") %} {%= filters.assessment_group %}
+{% if (filters.student_group){ %} +
{%= __("Student Group: ") %} {%= filters.student_group %}
+{% } %} +
+ + + + + {% for(var i=1, l=report_columns.length; i{%= report_columns[i].label %} + {% } %} + + + + {% for(var j=0, k=data.length; j + {% for(var i=1, l=report_columns.length; i + {% var fieldname = report_columns[i].fieldname; %} + {% if (!is_null(row[fieldname])) { %} + {%= row[fieldname] %} + {% } %} + + {% } %} + + {% } %} + +
+ +

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

diff --git a/education/education/report/course_wise_assessment_report/course_wise_assessment_report.js b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.js new file mode 100644 index 0000000..38c3c6d --- /dev/null +++ b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.js @@ -0,0 +1,40 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.query_reports["Course wise Assessment Report"] = { + "filters": [ + { + "fieldname":"academic_year", + "label": __("Academic Year"), + "fieldtype": "Link", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname":"academic_term", + "label": __("Academic Term"), + "fieldtype": "Link", + "options": "Academic Term" + }, + { + "fieldname":"course", + "label": __("Course"), + "fieldtype": "Link", + "options": "Course", + "reqd": 1 + }, + { + "fieldname":"student_group", + "label": __("Student Group"), + "fieldtype": "Link", + "options": "Student Group" + }, + { + "fieldname":"assessment_group", + "label": __("Assessment Group"), + "fieldtype": "Link", + "options": "Assessment Group", + "reqd": 1 + } + ] +}; diff --git a/education/education/report/course_wise_assessment_report/course_wise_assessment_report.json b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.json new file mode 100644 index 0000000..416db9d --- /dev/null +++ b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 0, + "creation": "2017-05-05 14:46:13.776133", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-06-24 17:15:15.477530", + "modified_by": "Administrator", + "module": "Education", + "name": "Course wise Assessment Report", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Assessment Result", + "report_name": "Course wise Assessment Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Instructor" + }, + { + "role": "Education Manager" + } + ] +} \ No newline at end of file diff --git a/education/education/report/course_wise_assessment_report/course_wise_assessment_report.py b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.py new file mode 100644 index 0000000..c1cdb36 --- /dev/null +++ b/education/education/report/course_wise_assessment_report/course_wise_assessment_report.py @@ -0,0 +1,322 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +from collections import OrderedDict, defaultdict + +import frappe +from frappe import _ + +from education.education.api import get_grade + + +def execute(filters=None): + data, chart, grades = [], [], [] + args = frappe._dict() + grade_wise_analysis = defaultdict(dict) + + args["academic_year"] = filters.get("academic_year") + args["course"] = filters.get("course") + args["assessment_group"] = filters.get("assessment_group") + + args["academic_term"] = filters.get("academic_term") + args["student_group"] = filters.get("student_group") + + if args["assessment_group"] == "All Assessment Groups": + frappe.throw( + _("Please select the assessment group other than 'All Assessment Groups'") + ) + + returned_values = get_formatted_result(args, get_assessment_criteria=True) + student_dict = returned_values["student_details"] + result_dict = returned_values["assessment_result"] + assessment_criteria_dict = returned_values["assessment_criteria"] + + for student in result_dict: + student_row = {} + student_row["student"] = student + student_row["student_name"] = student_dict[student] + for criteria in assessment_criteria_dict: + scrub_criteria = frappe.scrub(criteria) + if criteria in result_dict[student][args.course][args.assessment_group]: + student_row[scrub_criteria] = result_dict[student][args.course][ + args.assessment_group + ][criteria]["grade"] + student_row[scrub_criteria + "_score"] = result_dict[student][args.course][ + args.assessment_group + ][criteria]["score"] + + # create the list of possible grades + if student_row[scrub_criteria] not in grades: + grades.append(student_row[scrub_criteria]) + + # create the dict of for gradewise analysis + if student_row[scrub_criteria] not in grade_wise_analysis[criteria]: + grade_wise_analysis[criteria][student_row[scrub_criteria]] = 1 + else: + grade_wise_analysis[criteria][student_row[scrub_criteria]] += 1 + else: + student_row[frappe.scrub(criteria)] = "" + student_row[frappe.scrub(criteria) + "_score"] = "" + data.append(student_row) + + assessment_criteria_list = [d for d in assessment_criteria_dict] + columns = get_column(assessment_criteria_dict) + chart = get_chart_data(grades, assessment_criteria_list, grade_wise_analysis) + + return columns, data, None, chart + + +def get_formatted_result( + args, get_assessment_criteria=False, get_course=False, get_all_assessment_groups=False +): + cond, cond1, cond2, cond3, cond4 = " ", " ", " ", " ", " " + args_list = [args.academic_year] + + if args.course: + cond = " and ar.course=%s" + args_list.append(args.course) + + if args.academic_term: + cond1 = " and ar.academic_term=%s" + args_list.append(args.academic_term) + + if args.student_group: + cond2 = " and ar.student_group=%s" + args_list.append(args.student_group) + + create_total_dict = False + + assessment_groups = get_child_assessment_groups(args.assessment_group) + cond3 = " and ar.assessment_group in (%s)" % ( + ", ".join(["%s"] * len(assessment_groups)) + ) + args_list += assessment_groups + + if args.students: + cond4 = " and ar.student in (%s)" % (", ".join(["%s"] * len(args.students))) + args_list += args.students + + assessment_result = frappe.db.sql( + """ + SELECT + ar.student, ar.student_name, ar.academic_year, ar.academic_term, ar.program, ar.course, + ar.assessment_plan, ar.grading_scale, ar.assessment_group, ar.student_group, + ard.assessment_criteria, ard.maximum_score, ard.grade, ard.score + FROM + `tabAssessment Result` ar, `tabAssessment Result Detail` ard + WHERE + ar.name=ard.parent and ar.docstatus=1 and ar.academic_year=%s {0} {1} {2} {3} {4} + ORDER BY + ard.assessment_criteria""".format( + cond, cond1, cond2, cond3, cond4 + ), + tuple(args_list), + as_dict=1, + ) + + # create the nested dictionary structure as given below: + # ..... + # "Final Grade" -> assessment criteria used for totaling and args.assessment_group -> for totaling all the assesments + + student_details = {} + formatted_assessment_result = defaultdict(dict) + assessment_criteria_dict = OrderedDict() + course_dict = OrderedDict() + total_maximum_score = None + if not (len(assessment_groups) == 1 and assessment_groups[0] == args.assessment_group): + create_total_dict = True + + # add the score for a given score and recalculate the grades + def add_score_and_recalculate_grade(result, assessment_group, assessment_criteria): + formatted_assessment_result[result.student][result.course][assessment_group][ + assessment_criteria + ]["maximum_score"] += result.maximum_score + formatted_assessment_result[result.student][result.course][assessment_group][ + assessment_criteria + ]["score"] += result.score + tmp_grade = get_grade( + result.grading_scale, + ( + ( + formatted_assessment_result[result.student][result.course][assessment_group][ + assessment_criteria + ]["score"] + ) + / ( + formatted_assessment_result[result.student][result.course][assessment_group][ + assessment_criteria + ]["maximum_score"] + ) + ) + * 100, + ) + formatted_assessment_result[result.student][result.course][assessment_group][ + assessment_criteria + ]["grade"] = tmp_grade + + # create the assessment criteria "Final Grade" with the sum of all the scores of the assessment criteria in a given assessment group + def add_total_score(result, assessment_group): + if ( + "Final Grade" + not in formatted_assessment_result[result.student][result.course][assessment_group] + ): + formatted_assessment_result[result.student][result.course][assessment_group][ + "Final Grade" + ] = frappe._dict( + { + "assessment_criteria": "Final Grade", + "maximum_score": result.maximum_score, + "score": result.score, + "grade": result.grade, + } + ) + else: + add_score_and_recalculate_grade(result, assessment_group, "Final Grade") + + for result in assessment_result: + if result.student not in student_details: + student_details[result.student] = result.student_name + + assessment_criteria_details = frappe._dict( + { + "assessment_criteria": result.assessment_criteria, + "maximum_score": result.maximum_score, + "score": result.score, + "grade": result.grade, + } + ) + + if not formatted_assessment_result[result.student]: + formatted_assessment_result[result.student] = defaultdict(dict) + if not formatted_assessment_result[result.student][result.course]: + formatted_assessment_result[result.student][result.course] = defaultdict(dict) + + if not create_total_dict: + formatted_assessment_result[result.student][result.course][result.assessment_group][ + result.assessment_criteria + ] = assessment_criteria_details + add_total_score(result, result.assessment_group) + + # create the total of all the assessment groups criteria-wise + elif create_total_dict: + if get_all_assessment_groups: + formatted_assessment_result[result.student][result.course][result.assessment_group][ + result.assessment_criteria + ] = assessment_criteria_details + if not formatted_assessment_result[result.student][result.course][ + args.assessment_group + ]: + formatted_assessment_result[result.student][result.course][ + args.assessment_group + ] = defaultdict(dict) + formatted_assessment_result[result.student][result.course][args.assessment_group][ + result.assessment_criteria + ] = assessment_criteria_details + elif ( + result.assessment_criteria + not in formatted_assessment_result[result.student][result.course][ + args.assessment_group + ] + ): + formatted_assessment_result[result.student][result.course][args.assessment_group][ + result.assessment_criteria + ] = assessment_criteria_details + elif ( + result.assessment_criteria + in formatted_assessment_result[result.student][result.course][args.assessment_group] + ): + add_score_and_recalculate_grade( + result, args.assessment_group, result.assessment_criteria + ) + + add_total_score(result, args.assessment_group) + + total_maximum_score = formatted_assessment_result[result.student][result.course][ + args.assessment_group + ]["Final Grade"]["maximum_score"] + if get_assessment_criteria: + assessment_criteria_dict[result.assessment_criteria] = formatted_assessment_result[ + result.student + ][result.course][args.assessment_group][result.assessment_criteria][ + "maximum_score" + ] + if get_course: + course_dict[result.course] = total_maximum_score + + if get_assessment_criteria and total_maximum_score: + assessment_criteria_dict["Final Grade"] = total_maximum_score + + return { + "student_details": student_details, + "assessment_result": formatted_assessment_result, + "assessment_criteria": assessment_criteria_dict, + "course_dict": course_dict, + } + + +def get_column(assessment_criteria): + columns = [ + { + "fieldname": "student", + "label": _("Student ID"), + "fieldtype": "Link", + "options": "Student", + "width": 90, + }, + { + "fieldname": "student_name", + "label": _("Student Name"), + "fieldtype": "Data", + "width": 160, + }, + ] + for d in assessment_criteria: + columns.append( + {"fieldname": frappe.scrub(d), "label": d, "fieldtype": "Data", "width": 110} + ) + columns.append( + { + "fieldname": frappe.scrub(d) + "_score", + "label": "Score(" + str(int(assessment_criteria[d])) + ")", + "fieldtype": "Float", + "width": 100, + } + ) + + return columns + + +def get_chart_data(grades, criteria_list, kounter): + grades = sorted(grades) + datasets = [] + + for grade in grades: + tmp = frappe._dict({"name": grade, "values": []}) + for criteria in criteria_list: + if grade in kounter[criteria]: + tmp["values"].append(kounter[criteria][grade]) + else: + tmp["values"].append(0) + datasets.append(tmp) + + return { + "data": {"labels": criteria_list, "datasets": datasets}, + "type": "bar", + } + + +def get_child_assessment_groups(assessment_group): + assessment_groups = [] + group_type = frappe.get_value("Assessment Group", assessment_group, "is_group") + if group_type: + from frappe.desk.treeview import get_children + + assessment_groups = [ + d.get("value") + for d in get_children("Assessment Group", assessment_group) + if d.get("value") and not d.get("expandable") + ] + else: + assessment_groups = [assessment_group] + return assessment_groups diff --git a/education/education/report/final_assessment_grades/__init__.py b/education/education/report/final_assessment_grades/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/final_assessment_grades/final_assessment_grades.js b/education/education/report/final_assessment_grades/final_assessment_grades.js new file mode 100644 index 0000000..8b6ea9c --- /dev/null +++ b/education/education/report/final_assessment_grades/final_assessment_grades.js @@ -0,0 +1,38 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Final Assessment Grades"] = { + "filters": [ + { + "fieldname":"academic_year", + "label": __("Academic Year"), + "fieldtype": "Link", + "options": "Academic Year", + "reqd": 1 + }, + { + "fieldname":"student_group", + "label": __("Student Group"), + "fieldtype": "Link", + "options": "Student Group", + "reqd": 1, + "get_query": function() { + return{ + filters: { + "group_based_on": "Batch", + "academic_year": frappe.query_report.get_filter_value('academic_year') + } + }; + } + }, + { + "fieldname":"assessment_group", + "label": __("Assessment Group"), + "fieldtype": "Link", + "options": "Assessment Group", + "reqd": 1 + } + + ] +} diff --git a/education/education/report/final_assessment_grades/final_assessment_grades.json b/education/education/report/final_assessment_grades/final_assessment_grades.json new file mode 100644 index 0000000..6a23494 --- /dev/null +++ b/education/education/report/final_assessment_grades/final_assessment_grades.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "creation": "2018-01-22 17:04:43.412054", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-06-24 17:13:35.373756", + "modified_by": "Administrator", + "module": "Education", + "name": "Final Assessment Grades", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Assessment Result", + "report_name": "Final Assessment Grades", + "report_type": "Script Report", + "roles": [ + { + "role": "Instructor" + }, + { + "role": "Education Manager" + } + ] +} \ No newline at end of file diff --git a/education/education/report/final_assessment_grades/final_assessment_grades.py b/education/education/report/final_assessment_grades/final_assessment_grades.py new file mode 100644 index 0000000..161317a --- /dev/null +++ b/education/education/report/final_assessment_grades/final_assessment_grades.py @@ -0,0 +1,99 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +from collections import defaultdict + +import frappe +from frappe import _ + +from education.education.report.course_wise_assessment_report.course_wise_assessment_report import ( + get_chart_data, get_formatted_result) + + +def execute(filters=None): + columns, data, grades = [], [], [] + args = frappe._dict() + course_wise_analysis = defaultdict(dict) + + args["academic_year"] = filters.get("academic_year") + assessment_group = args["assessment_group"] = filters.get("assessment_group") + + student_group = filters.get("student_group") + args.students = frappe.db.sql_list( + "select student from `tabStudent Group Student` where parent=%s", (student_group) + ) + + values = get_formatted_result(args, get_course=True) + student_details = values.get("student_details") + assessment_result = values.get("assessment_result") + course_dict = values.get("course_dict") + + for student in args.students: + if student_details.get(student): + student_row = {} + student_row["student"] = student + student_row["student_name"] = student_details[student] + for course in course_dict: + scrub_course = frappe.scrub(course) + if assessment_group in assessment_result[student][course]: + student_row["grade_" + scrub_course] = assessment_result[student][course][ + assessment_group + ]["Total Score"]["grade"] + student_row["score_" + scrub_course] = assessment_result[student][course][ + assessment_group + ]["Total Score"]["score"] + + # create the list of possible grades + if student_row["grade_" + scrub_course] not in grades: + grades.append(student_row["grade_" + scrub_course]) + + # create the dict of for gradewise analysis + if student_row["grade_" + scrub_course] not in course_wise_analysis[course]: + course_wise_analysis[course][student_row["grade_" + scrub_course]] = 1 + else: + course_wise_analysis[course][student_row["grade_" + scrub_course]] += 1 + + data.append(student_row) + + course_list = [d for d in course_dict] + columns = get_column(course_dict) + chart = get_chart_data(grades, course_list, course_wise_analysis) + return columns, data, None, chart + + +def get_column(course_dict): + columns = [ + { + "fieldname": "student", + "label": _("Student ID"), + "fieldtype": "Link", + "options": "Student", + "width": 90, + }, + { + "fieldname": "student_name", + "label": _("Student Name"), + "fieldtype": "Data", + "width": 160, + }, + ] + for course in course_dict: + columns.append( + { + "fieldname": "grade_" + frappe.scrub(course), + "label": course, + "fieldtype": "Data", + "width": 110, + } + ) + columns.append( + { + "fieldname": "score_" + frappe.scrub(course), + "label": "Score(" + str(course_dict[course]) + ")", + "fieldtype": "Float", + "width": 100, + } + ) + + return columns diff --git a/education/education/report/program_wise_fee_collection/__init__.py b/education/education/report/program_wise_fee_collection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/program_wise_fee_collection/program_wise_fee_collection.js b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.js new file mode 100644 index 0000000..e3e8c49 --- /dev/null +++ b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.js @@ -0,0 +1,22 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Program wise Fee Collection"] = { + "filters": [ + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "reqd": 1 + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1 + } + ] +}; diff --git a/education/education/report/program_wise_fee_collection/program_wise_fee_collection.json b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.json new file mode 100644 index 0000000..ee5c0de --- /dev/null +++ b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 1, + "creation": "2020-07-27 16:05:33.263539", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2020-08-05 14:14:12.410515", + "modified_by": "Administrator", + "module": "Education", + "name": "Program wise Fee Collection", + "owner": "Administrator", + "prepared_report": 0, + "query": "SELECT \n FeesCollected.program AS \"Program:Link/Program:200\",\n FeesCollected.paid_amount AS \"Fees Collected:Currency:150\",\n FeesCollected.outstanding_amount AS \"Outstanding Amount:Currency:150\",\n FeesCollected.grand_total \"Grand Total:Currency:150\"\nFROM (\n SELECT \n sum(grand_total) - sum(outstanding_amount) AS paid_amount, program,\n sum(outstanding_amount) AS outstanding_amount,\n sum(grand_total) AS grand_total\n FROM `tabFees`\n WHERE docstatus = 1\n GROUP BY program\n) AS FeesCollected\nORDER BY FeesCollected.paid_amount DESC", + "ref_doctype": "Fees", + "report_name": "Program wise Fee Collection", + "report_type": "Script Report", + "roles": [ + { + "role": "Academics User" + }, + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + } + ] +} \ No newline at end of file diff --git a/education/education/report/program_wise_fee_collection/program_wise_fee_collection.py b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.py new file mode 100644 index 0000000..5479382 --- /dev/null +++ b/education/education/report/program_wise_fee_collection/program_wise_fee_collection.py @@ -0,0 +1,127 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ + + +def execute(filters=None): + if not filters: + filters = {} + + columns = get_columns(filters) + data = get_data(filters) + chart = get_chart_data(data) + + return columns, data, None, chart + + +def get_columns(filters=None): + return [ + { + "label": _("Program"), + "fieldname": "program", + "fieldtype": "Link", + "options": "Program", + "width": 300, + }, + { + "label": _("Fees Collected"), + "fieldname": "fees_collected", + "fieldtype": "Currency", + "width": 200, + }, + { + "label": _("Outstanding Amount"), + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "width": 200, + }, + { + "label": _("Grand Total"), + "fieldname": "grand_total", + "fieldtype": "Currency", + "width": 200, + }, + ] + + +def get_data(filters=None): + data = [] + + conditions = get_filter_conditions(filters) + + fee_details = frappe.db.sql( + """ + SELECT + FeesCollected.program, + FeesCollected.paid_amount, + FeesCollected.outstanding_amount, + FeesCollected.grand_total + FROM ( + SELECT + sum(grand_total) - sum(outstanding_amount) AS paid_amount, program, + sum(outstanding_amount) AS outstanding_amount, + sum(grand_total) AS grand_total + FROM `tabFees` + WHERE + docstatus = 1 and + program IS NOT NULL + %s + GROUP BY program + ) AS FeesCollected + ORDER BY FeesCollected.paid_amount DESC + """ + % (conditions), + as_dict=1, + ) + + for entry in fee_details: + data.append( + { + "program": entry.program, + "fees_collected": entry.paid_amount, + "outstanding_amount": entry.outstanding_amount, + "grand_total": entry.grand_total, + } + ) + + return data + + +def get_filter_conditions(filters): + conditions = "" + + if filters.get("from_date") and filters.get("to_date"): + conditions += " and posting_date BETWEEN '%s' and '%s'" % ( + filters.get("from_date"), + filters.get("to_date"), + ) + + return conditions + + +def get_chart_data(data): + if not data: + return + + labels = [] + fees_collected = [] + outstanding_amount = [] + + for entry in data: + labels.append(entry.get("program")) + fees_collected.append(entry.get("fees_collected")) + outstanding_amount.append(entry.get("outstanding_amount")) + + return { + "data": { + "labels": labels, + "datasets": [ + {"name": _("Fees Collected"), "values": fees_collected}, + {"name": _("Outstanding Amt"), "values": outstanding_amount}, + ], + }, + "type": "bar", + } diff --git a/education/education/report/student_and_guardian_contact_details/__init__.py b/education/education/report/student_and_guardian_contact_details/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.js b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.js new file mode 100644 index 0000000..dc2e320 --- /dev/null +++ b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.js @@ -0,0 +1,29 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.query_reports["Student and Guardian Contact Details"] = { + "filters": [ + { + "fieldname":"academic_year", + "label": __("Academic Year"), + "fieldtype": "Link", + "options": "Academic Year", + "reqd": 1, + }, + { + "fieldname":"program", + "label": __("Program"), + "fieldtype": "Link", + "options": "Program", + "reqd": 1 + }, + { + "fieldname":"student_batch_name", + "label": __("Batch Name"), + "fieldtype": "Link", + "options": "Student Batch Name", + "reqd": 1 + }, + + ] +} diff --git a/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json new file mode 100644 index 0000000..fa9be65 --- /dev/null +++ b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "creation": "2017-03-27 17:47:16.831433", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-06-24 17:16:50.639488", + "modified_by": "Administrator", + "module": "Education", + "name": "Student and Guardian Contact Details", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Program Enrollment", + "report_name": "Student and Guardian Contact Details", + "report_type": "Script Report", + "roles": [ + { + "role": "Instructor" + }, + { + "role": "Academics User" + } + ] +} \ No newline at end of file diff --git a/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py new file mode 100644 index 0000000..483c4bb --- /dev/null +++ b/education/education/report/student_and_guardian_contact_details/student_and_guardian_contact_details.py @@ -0,0 +1,156 @@ +# Copyright (c) 2013, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ + + +def execute(filters=None): + columns, data = [], [] + + academic_year = filters.get("academic_year") + program = filters.get("program") + student_batch_name = filters.get("student_batch_name") + + columns = get_columns() + + program_enrollments = frappe.get_list( + "Program Enrollment", + fields=["student", "student_name"], + filters={ + "academic_year": academic_year, + "program": program, + "student_batch_name": student_batch_name, + }, + ) + + student_list = [d.student for d in program_enrollments] + if not student_list: + return columns, [] + + group_roll_no_map = get_student_roll_no(academic_year, program, student_batch_name) + student_map = get_student_details(student_list) + guardian_map = get_guardian_map(student_list) + + for d in program_enrollments: + student_details = student_map.get(d.student) + row = [ + group_roll_no_map.get(d.student), + d.student, + d.student_name, + student_details.get("student_mobile_number"), + student_details.get("student_email_id"), + student_details.get("address"), + ] + + student_guardians = guardian_map.get(d.student) + + if student_guardians: + for i in range(2): + if i < len(student_guardians): + g = student_guardians[i] + row += [g.guardian_name, g.relation, g.mobile_number, g.email_address] + + data.append(row) + + return columns, data + + +def get_columns(): + columns = [ + _("Group Roll No") + "::60", + _("Student ID") + ":Link/Student:90", + _("Student Name") + "::150", + _("Student Mobile No.") + "::110", + _("Student Email ID") + "::125", + _("Student Address") + "::175", + _("Guardian1 Name") + "::150", + _("Relation with Guardian1") + "::80", + _("Guardian1 Mobile No") + "::125", + _("Guardian1 Email ID") + "::125", + _("Guardian2 Name") + "::150", + _("Relation with Guardian2") + "::80", + _("Guardian2 Mobile No") + "::125", + _("Guardian2 Email ID") + "::125", + ] + return columns + + +def get_student_details(student_list): + student_map = frappe._dict() + student_details = frappe.db.sql( + """ + select name, student_mobile_number, student_email_id, address_line_1, address_line_2, city, state from `tabStudent` where name in (%s)""" + % ", ".join(["%s"] * len(student_list)), + tuple(student_list), + as_dict=1, + ) + for s in student_details: + student = frappe._dict() + student["student_mobile_number"] = s.student_mobile_number + student["student_email_id"] = s.student_email_id + student["address"] = ", ".join( + [d for d in [s.address_line_1, s.address_line_2, s.city, s.state] if d] + ) + student_map[s.name] = student + return student_map + + +def get_guardian_map(student_list): + guardian_map = frappe._dict() + guardian_details = frappe.db.sql( + """ + select parent, guardian, guardian_name, relation from `tabStudent Guardian` where parent in (%s)""" + % ", ".join(["%s"] * len(student_list)), + tuple(student_list), + as_dict=1, + ) + + guardian_list = list(set([g.guardian for g in guardian_details])) or [""] + + guardian_mobile_no = dict( + frappe.db.sql( + """select name, mobile_number from `tabGuardian` + where name in (%s)""" + % ", ".join(["%s"] * len(guardian_list)), + tuple(guardian_list), + ) + ) + + guardian_email_id = dict( + frappe.db.sql( + """select name, email_address from `tabGuardian` + where name in (%s)""" + % ", ".join(["%s"] * len(guardian_list)), + tuple(guardian_list), + ) + ) + + for guardian in guardian_details: + guardian["mobile_number"] = guardian_mobile_no.get(guardian.guardian) + guardian["email_address"] = guardian_email_id.get(guardian.guardian) + guardian_map.setdefault(guardian.parent, []).append(guardian) + + return guardian_map + + +def get_student_roll_no(academic_year, program, batch): + student_group = frappe.get_all( + "Student Group", + filters={ + "academic_year": academic_year, + "program": program, + "batch": batch, + "disabled": 0, + }, + ) + if student_group: + roll_no_dict = dict( + frappe.db.sql( + """select student, group_roll_number from `tabStudent Group Student` where parent=%s""", + (student_group[0].name), + ) + ) + return roll_no_dict + return {} diff --git a/education/education/report/student_batch_wise_attendance/__init__.py b/education/education/report/student_batch_wise_attendance/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js new file mode 100644 index 0000000..70c9a9b --- /dev/null +++ b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.js @@ -0,0 +1,12 @@ +// Copyright (c) 2016, InfluxERP +// For license information, please see license.txt + +frappe.query_reports["Student Batch-Wise Attendance"] = { + "filters": [{ + "fieldname": "date", + "label": __("Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1 + }] +} diff --git a/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json new file mode 100644 index 0000000..8baf8f9 --- /dev/null +++ b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "creation": "2016-11-28 22:07:03.859124", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 2, + "is_standard": "Yes", + "modified": "2020-06-24 17:16:59.823709", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Batch-Wise Attendance", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Student Attendance", + "report_name": "Student Batch-Wise Attendance", + "report_type": "Script Report", + "roles": [ + { + "role": "Academics User" + } + ] +} \ No newline at end of file diff --git a/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py new file mode 100644 index 0000000..40a1747 --- /dev/null +++ b/education/education/report/student_batch_wise_attendance/student_batch_wise_attendance.py @@ -0,0 +1,92 @@ +# Copyright (c) 2015, InfluxERP +# License: GNU General Public License v3. See license.txt + + +import frappe +from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday +from frappe import _, msgprint +from frappe.utils import formatdate + +from education.education.doctype.student_attendance.student_attendance import \ + get_holiday_list + + +def execute(filters=None): + if not filters: + filters = {} + + 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() + + data = [] + for student_group in active_student_group: + row = [student_group.name] + present_students = 0 + absent_students = 0 + student_group_strength = get_student_group_strength(student_group.name) + student_attendance = get_student_attendance(student_group.name, filters.get("date")) + if student_attendance: + for attendance in student_attendance: + if attendance.status == "Present": + present_students = attendance.count + elif attendance.status == "Absent": + absent_students = attendance.count + + unmarked_students = student_group_strength - (present_students + absent_students) + row += [student_group_strength, present_students, absent_students, unmarked_students] + data.append(row) + + return columns, data + + +def get_columns(filters): + 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" + 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` + 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 docstatus = 1 and + (course_schedule is Null or course_schedule='') group by status""", + (student_group, date), + as_dict=1, + ) + return student_attendance diff --git a/education/education/report/student_fee_collection/__init__.py b/education/education/report/student_fee_collection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/student_fee_collection/student_fee_collection.json b/education/education/report/student_fee_collection/student_fee_collection.json new file mode 100644 index 0000000..c0229a2 --- /dev/null +++ b/education/education/report/student_fee_collection/student_fee_collection.json @@ -0,0 +1,25 @@ +{ + "add_total_row": 1, + "creation": "2016-06-22 02:58:41.024538", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 3, + "is_standard": "Yes", + "modified": "2020-06-24 17:14:39.452551", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Fee Collection", + "owner": "Administrator", + "prepared_report": 0, + "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nWHERE\n docstatus=1 \nGROUP BY\n student", + "ref_doctype": "Fees", + "report_name": "Student Fee Collection", + "report_type": "Query Report", + "roles": [ + { + "role": "Academics User" + } + ] +} diff --git a/education/education/report/student_monthly_attendance_sheet/__init__.py b/education/education/report/student_monthly_attendance_sheet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js new file mode 100644 index 0000000..34a2cb2 --- /dev/null +++ b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.js @@ -0,0 +1,42 @@ +// Copyright (c) 2015, InfluxERP +// License: GNU General Public License v3. See license.txt + + +frappe.query_reports["Student Monthly Attendance Sheet"] = { + "filters": [{ + "fieldname": "month", + "label": __("Month"), + "fieldtype": "Select", + "options": "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec", + "default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", + "Dec" + ][frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth()], + }, + { + "fieldname": "year", + "label": __("Year"), + "fieldtype": "Select", + "reqd": 1 + }, + { + "fieldname": "student_group", + "label": __("Student Group"), + "fieldtype": "Link", + "options": "Student Group", + "reqd": 1 + } + ], + + "onload": function() { + return frappe.call({ + method: "education.education.report.student_monthly_attendance_sheet.student_monthly_attendance_sheet.get_attendance_years", + callback: function(r) { + var year_filter = frappe.query_report.get_filter('year'); + year_filter.df.options = r.message; + year_filter.df.default = r.message.split("\n")[0]; + year_filter.refresh(); + year_filter.set_input(year_filter.df.default); + } + }); + } +} diff --git a/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json new file mode 100644 index 0000000..1423d4f --- /dev/null +++ b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.json @@ -0,0 +1,24 @@ +{ + "add_total_row": 0, + "creation": "2013-05-13 14:04:03", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 3, + "is_standard": "Yes", + "modified": "2020-06-24 17:16:13.307053", + "modified_by": "Administrator", + "module": "Education", + "name": "Student Monthly Attendance Sheet", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Student Attendance", + "report_name": "Student Monthly Attendance Sheet", + "report_type": "Script Report", + "roles": [ + { + "role": "Academics User" + } + ] +} \ No newline at end of file diff --git a/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py new file mode 100644 index 0000000..8dbb31d --- /dev/null +++ b/education/education/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py @@ -0,0 +1,164 @@ +# Copyright (c) 2015, InfluxERP +# License: GNU General Public License v3. See license.txt + + +import frappe +from erpnext.support.doctype.issue.issue import get_holidays +from frappe import _ +from frappe.utils import (add_days, cstr, date_diff, get_first_day, + get_last_day, getdate) + +from education.education.api import get_student_group_students +from education.education.doctype.student_attendance.student_attendance import \ + get_holiday_list + + +def execute(filters=None): + if not filters: + filters = {} + + from_date = get_first_day(filters["month"] + "-" + filters["year"]) + to_date = get_last_day(filters["month"] + "-" + filters["year"]) + total_days_in_month = date_diff(to_date, from_date) + 1 + columns = get_columns(total_days_in_month) + students = get_student_group_students(filters.get("student_group"), 1) + 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": "-", + "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 + + +def get_columns(days_in_month): + columns = [_("Student") + ":Link/Student:90", _("Student Name") + "::150"] + for day in range(days_in_month): + columns.append(cstr(day + 1) + "::20") + columns += [_("Total Present") + ":Int:95", _("Total Absent") + ":Int:90"] + return columns + + +def get_students_list(students): + student_list = [] + for stud in students: + student_list.append(stud.student) + 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 + and docstatus = 1 + 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): + if not students_list: + return + leave_applications = frappe.db.sql( + """ + 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 + or to_date between %(from_date)s and %(to_date)s + or (%(from_date)s between from_date and to_date and %(to_date)s between from_date and to_date) + ) + """, + {"students": students_list, "from_date": from_date, "to_date": to_date}, + as_dict=True, + ) + students_with_leaves = {} + for application in leave_applications: + for date in daterange(application.from_date, application.to_date): + students_with_leaves.setdefault(date, []).append(application.student) + + return students_with_leaves + + +def daterange(d1, d2): + import datetime + + return (d1 + datetime.timedelta(days=i) for i in range((d2 - d1).days + 1)) + + +@frappe.whitelist() +def get_attendance_years(): + year_list = frappe.db.sql_list( + """select distinct YEAR(date) from `tabStudent Attendance` ORDER BY YEAR(date) DESC""" + ) + 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 diff --git a/education/education/setup.py b/education/education/setup.py new file mode 100644 index 0000000..a583fae --- /dev/null +++ b/education/education/setup.py @@ -0,0 +1,98 @@ +# Copyright (c) 2017, InfluxERP +# For license information, please see license.txt + + +import frappe +from frappe import _ + +# add data for doctypes +data = { + 'desktop_icons': [ + 'Student', + 'Program', + 'Course', + 'Student Group', + 'Instructor', + 'Fees', + 'Task', + 'ToDo', + 'Education', + 'Student Attendance Tool', + 'Student Applicant' + ], + 'default_portal_role': 'Student', + 'restricted_roles': [ + 'Student', + 'Instructor', + 'Academics User', + 'Education Manager' + ] +} + + + +def setup_education(): + disable_desk_access_for_student_role() + if frappe.db.exists("Academic Year", "2015-16"): + # already setup + return + create_academic_sessions() + + +def insert_record(records): + for r in records: + doc = frappe.new_doc(r.get("doctype")) + doc.update(r) + try: + doc.insert(ignore_permissions=True) + except frappe.DuplicateEntryError as e: + # pass DuplicateEntryError and continue + if e.args and e.args[0] == doc.doctype and e.args[1] == doc.name: + # make sure DuplicateEntryError is for the exact same doc and not a related doc + pass + else: + raise + + +def create_academic_sessions(): + data = [ + {"doctype": "Academic Year", "academic_year_name": "2015-16"}, + {"doctype": "Academic Year", "academic_year_name": "2016-17"}, + {"doctype": "Academic Year", "academic_year_name": "2017-18"}, + {"doctype": "Academic Year", "academic_year_name": "2018-19"}, + {"doctype": "Academic Term", "academic_year": "2016-17", "term_name": "Semester 1"}, + {"doctype": "Academic Term", "academic_year": "2016-17", "term_name": "Semester 2"}, + {"doctype": "Academic Term", "academic_year": "2017-18", "term_name": "Semester 1"}, + {"doctype": "Academic Term", "academic_year": "2017-18", "term_name": "Semester 2"}, + { + "doctype": "Assessment Group", + "assessment_group_name": _("All Assessment Groups"), + "is_group": 1, + "parent_assessment_group": "", + }, + {"doctype": "Party Type", "party_type": "Student", "account_type": "Receivable"}, + ] + insert_record(data) + + +def disable_desk_access_for_student_role(): + try: + student_role = frappe.get_doc("Role", "Student") + except frappe.DoesNotExistError: + create_student_role() + return + + student_role.desk_access = 0 + student_role.save() + + +def create_student_role(): + student_role = frappe.get_doc( + { + "doctype": "Role", + "role_name": "Student", + "desk_access": 0, + "restrict_to_domain": "Education", + } + ) + student_role.insert() diff --git a/education/education/utils.py b/education/education/utils.py new file mode 100644 index 0000000..a700ef4 --- /dev/null +++ b/education/education/utils.py @@ -0,0 +1,476 @@ +# Copyright (c) 2015, InfluxERP + +import frappe +from frappe import _ + + +class OverlapError(frappe.ValidationError): + pass + + +def validate_overlap_for(doc, doctype, fieldname, value=None): + """Checks overlap for specified field. + + :param fieldname: Checks Overlap for this field + """ + + existing = get_overlap_for(doc, doctype, fieldname, value) + if existing: + frappe.throw( + _("This {0} conflicts with {1} for {2} {3}").format( + doc.doctype, + existing.name, + doc.meta.get_label(fieldname) if not value else fieldname, + value or doc.get(fieldname), + ), + OverlapError, + ) + + +def get_overlap_for(doc, doctype, fieldname, value=None): + """Returns overlaping document for specified field. + + :param fieldname: Checks Overlap for this field + """ + + existing = frappe.db.sql( + """select name, from_time, to_time from `tab{0}` + where `{1}`=%(val)s and schedule_date = %(schedule_date)s and + ( + (from_time > %(from_time)s and from_time < %(to_time)s) or + (to_time > %(from_time)s and to_time < %(to_time)s) or + (%(from_time)s > from_time and %(from_time)s < to_time) or + (%(from_time)s = from_time and %(to_time)s = to_time)) + and name!=%(name)s and docstatus!=2""".format( + doctype, fieldname + ), + { + "schedule_date": doc.schedule_date, + "val": value or doc.get(fieldname), + "from_time": doc.from_time, + "to_time": doc.to_time, + "name": doc.name or "No Name", + }, + as_dict=True, + ) + + return existing[0] if existing else None + + +def validate_duplicate_student(students): + unique_students = [] + for stud in students: + if stud.student in unique_students: + frappe.throw( + _("Student {0} - {1} appears Multiple times in row {2} & {3}").format( + stud.student, stud.student_name, unique_students.index(stud.student) + 1, stud.idx + ) + ) + else: + unique_students.append(stud.student) + + return None + + +# LMS Utils +def get_current_student(): + """Returns current student from frappe.session.user + + Returns: + object: Student Document + """ + email = frappe.session.user + if email in ("Administrator", "Guest"): + return None + try: + student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name + return frappe.get_doc("Student", student_id) + except (IndexError, frappe.DoesNotExistError): + return None + + +def get_portal_programs(): + """Returns a list of all program to be displayed on the portal + Programs are returned based on the following logic + is_published and (student_is_enrolled or student_can_self_enroll) + + Returns: + list of dictionary: List of all programs and to be displayed on the portal along with access rights + """ + published_programs = frappe.get_all("Program", filters={"is_published": True}) + if not published_programs: + return None + + program_list = [frappe.get_doc("Program", program) for program in published_programs] + portal_programs = [ + {"program": program, "has_access": allowed_program_access(program.name)} + for program in program_list + if allowed_program_access(program.name) or program.allow_self_enroll + ] + + return portal_programs + + +def allowed_program_access(program, student=None): + """Returns enrollment status for current student + + Args: + program (string): Name of the program + student (object): instance of Student document + + Returns: + bool: Is current user enrolled or not + """ + if has_super_access(): + return True + if not student: + student = get_current_student() + if student and get_enrollment("program", program, student.name): + return True + else: + return False + + +def get_enrollment(master, document, student): + """Gets enrollment for course or program + + Args: + master (string): can either be program or course + document (string): program or course name + student (string): Student ID + + Returns: + string: Enrollment Name if exists else returns empty string + """ + if master == "program": + enrollments = frappe.get_all( + "Program Enrollment", + filters={"student": student, "program": document, "docstatus": 1}, + ) + if master == "course": + enrollments = frappe.get_all( + "Course Enrollment", filters={"student": student, "course": document} + ) + + if enrollments: + return enrollments[0].name + else: + return None + + +@frappe.whitelist() +def enroll_in_program(program_name, student=None): + """Enroll student in program + + Args: + program_name (string): Name of the program to be enrolled into + student (string, optional): name of student who has to be enrolled, if not + provided, a student will be created from the current user + + Returns: + string: name of the program enrollment document + """ + if has_super_access(): + return + + if not student == None: + student = frappe.get_doc("Student", student) + else: + # Check if self enrollment in allowed + program = frappe.get_doc("Program", program_name) + if not program.allow_self_enroll: + return frappe.throw(_("You are not allowed to enroll for this course")) + + student = get_current_student() + if not student: + student = create_student_from_current_user() + + # Check if student is already enrolled in program + enrollment = get_enrollment("program", program_name, student.name) + if enrollment: + return enrollment + + # Check if self enrollment in allowed + program = frappe.get_doc("Program", program_name) + if not program.allow_self_enroll: + return frappe.throw(_("You are not allowed to enroll for this course")) + + # Enroll in program + program_enrollment = student.enroll_in_program(program_name) + return program_enrollment.name + + +def has_super_access(): + """Check if user has a role that allows full access to LMS + + Returns: + bool: true if user has access to all lms content + """ + current_user = frappe.get_doc("User", frappe.session.user) + roles = set([role.role for role in current_user.roles]) + return bool( + roles + & { + "Administrator", + "Instructor", + "Education Manager", + "System Manager", + "Academic User", + } + ) + + +@frappe.whitelist() +def add_activity(course, content_type, content, program): + if has_super_access(): + return None + + student = get_current_student() + if not student: + return frappe.throw( + _("Student with email {0} does not exist").format(frappe.session.user), + frappe.DoesNotExistError, + ) + + enrollment = get_or_create_course_enrollment(course, program) + if content_type == "Quiz": + return + else: + return enrollment.add_activity(content_type, content) + + +@frappe.whitelist() +def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken): + import json + + student = get_current_student() + + quiz_response = json.loads(quiz_response) + quiz = frappe.get_doc("Quiz", quiz_name) + result, score, status = quiz.evaluate(quiz_response, quiz_name) + + if has_super_access(): + return {"result": result, "score": score, "status": status} + + if student: + enrollment = get_or_create_course_enrollment(course, program) + if quiz.allowed_attempt(enrollment, quiz_name): + enrollment.add_quiz_activity( + quiz_name, quiz_response, result, score, status, time_taken + ) + return {"result": result, "score": score, "status": status} + else: + return None + + +@frappe.whitelist() +def get_quiz(quiz_name, course): + try: + quiz = frappe.get_doc("Quiz", quiz_name) + questions = quiz.get_questions() + except Exception: + frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError) + return None + + questions = [ + { + "name": question.name, + "question": question.question, + "type": question.question_type, + "options": [ + {"name": option.name, "option": option.option} for option in question.options + ], + } + for question in questions + ] + + if has_super_access(): + return { + "questions": questions, + "activity": None, + "is_time_bound": quiz.is_time_bound, + "duration": quiz.duration, + } + + student = get_current_student() + course_enrollment = get_enrollment("course", course, student.name) + status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment) + return { + "questions": questions, + "activity": { + "is_complete": status, + "score": score, + "result": result, + "time_taken": time_taken, + }, + "is_time_bound": quiz.is_time_bound, + "duration": quiz.duration, + } + + +def get_topic_progress(topic, course_name, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + student = get_current_student() + if not student: + return None + course_enrollment = get_or_create_course_enrollment(course_name, program) + progress = student.get_topic_progress(course_enrollment.name, topic) + if not progress: + return None + count = sum([activity["is_complete"] for activity in progress]) + if count == 0: + return {"completed": False, "started": False} + elif count == len(progress): + return {"completed": True, "started": True} + elif count < len(progress): + return {"completed": False, "started": True} + + +def get_course_progress(course, program): + """ + Return the porgress of a course in a program as well as the content to continue from. + :param topic_name: + :param course_name: + """ + course_progress = [] + for course_topic in course.topics: + topic = frappe.get_doc("Topic", course_topic.topic) + progress = get_topic_progress(topic, course.name, program) + if progress: + course_progress.append(progress) + if course_progress: + number_of_completed_topics = sum( + [activity["completed"] for activity in course_progress] + ) + total_topics = len(course_progress) + if total_topics == 1: + return course_progress[0] + if number_of_completed_topics == 0: + return {"completed": False, "started": False} + if number_of_completed_topics == total_topics: + return {"completed": True, "started": True} + if number_of_completed_topics < total_topics: + return {"completed": False, "started": True} + + return None + + +def get_program_progress(program): + program_progress = [] + if not program.courses: + return None + for program_course in program.courses: + course = frappe.get_doc("Course", program_course.course) + progress = get_course_progress(course, program.name) + if progress: + progress["name"] = course.name + progress["course"] = course.course_name + program_progress.append(progress) + + if program_progress: + return program_progress + + return None + + +def get_program_completion(program): + topics = frappe.db.sql( + """select `tabCourse Topic`.topic, `tabCourse Topic`.parent + from `tabCourse Topic`, + `tabProgram Course` + where `tabCourse Topic`.parent = `tabProgram Course`.course + and `tabProgram Course`.parent = %s""", + program.name, + ) + + progress = [] + for topic in topics: + topic_doc = frappe.get_doc("Topic", topic[0]) + topic_progress = get_topic_progress(topic_doc, topic[1], program.name) + if topic_progress: + progress.append(topic_progress) + + if progress: + number_of_completed_topics = sum( + [activity["completed"] for activity in progress if activity] + ) + total_topics = len(progress) + try: + return int((float(number_of_completed_topics) / total_topics) * 100) + except ZeroDivisionError: + return 0 + + return 0 + + +def create_student_from_current_user(): + user = frappe.get_doc("User", frappe.session.user) + + student = frappe.get_doc( + { + "doctype": "Student", + "first_name": user.first_name, + "last_name": user.last_name, + "student_email_id": user.email, + "user": frappe.session.user, + } + ) + + student.save(ignore_permissions=True) + return student + + +def get_or_create_course_enrollment(course, program): + student = get_current_student() + course_enrollment = get_enrollment("course", course, student.name) + if not course_enrollment: + program_enrollment = get_enrollment("program", program.name, student.name) + if not program_enrollment: + frappe.throw(_("You are not enrolled in program {0}").format(program)) + return + return student.enroll_in_course( + course_name=course, + program_enrollment=get_enrollment("program", program.name, student.name), + ) + else: + return frappe.get_doc("Course Enrollment", course_enrollment) + + +def check_content_completion(content_name, content_type, enrollment_name): + activity = frappe.get_all( + "Course Activity", + filters={ + "enrollment": enrollment_name, + "content_type": content_type, + "content": content_name, + }, + ) + if activity: + return True + else: + return False + + +def check_quiz_completion(quiz, enrollment_name): + attempts = frappe.get_all( + "Quiz Activity", + filters={"enrollment": enrollment_name, "quiz": quiz.name}, + fields=["name", "activity_date", "score", "status", "time_taken"], + ) + status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts) + score = None + result = None + time_taken = None + if attempts: + if quiz.grading_basis == "Last Highest Score": + attempts = sorted(attempts, key=lambda i: int(i.score), reverse=True) + score = attempts[0]["score"] + result = attempts[0]["status"] + time_taken = attempts[0]["time_taken"] + if result == "Pass": + status = True + return status, score, result, time_taken diff --git a/education/education/workspace/education/education.json b/education/education/workspace/education/education.json new file mode 100644 index 0000000..7fd8576 --- /dev/null +++ b/education/education/workspace/education/education.json @@ -0,0 +1,765 @@ +{ + "charts": [ + { + "chart_name": "Program Enrollments", + "label": "Program Enrollments" + } + ], + "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Education\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Program Enrollments\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Instructor\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Program\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fees\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Monthly Attendance Sheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Course Scheduling Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Student Attendance Tool\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Student and Instructor\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Content Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Admission\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Fees\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Schedule\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Attendance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"LMS Activity\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assessment Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", + "creation": "2020-03-02 17:22:57.066401", + "docstatus": 0, + "doctype": "Workspace", + "for_user": "", + "hide_custom": 0, + "icon": "education", + "idx": 0, + "label": "Education", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Student and Instructor", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student", + "link_count": 0, + "link_to": "Student", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Instructor", + "link_count": 0, + "link_to": "Instructor", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Guardian", + "link_count": 0, + "link_to": "Guardian", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Group", + "link_count": 0, + "link_to": "Student Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Log", + "link_count": 0, + "link_to": "Student Log", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Masters", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program", + "link_count": 0, + "link_to": "Program", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course", + "link_count": 0, + "link_to": "Course", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Topic", + "link_count": 0, + "link_to": "Topic", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Room", + "link_count": 0, + "link_to": "Room", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Content Masters", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Article", + "link_count": 0, + "link_to": "Article", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Video", + "link_count": 0, + "link_to": "Video", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quiz", + "link_count": 0, + "link_to": "Quiz", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Education Settings", + "link_count": 0, + "link_to": "Education Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Category", + "link_count": 0, + "link_to": "Student Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Batch Name", + "link_count": 0, + "link_to": "Student Batch Name", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Grading Scale", + "link_count": 0, + "link_to": "Grading Scale", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Academic Term", + "link_count": 0, + "link_to": "Academic Term", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Academic Year", + "link_count": 0, + "link_to": "Academic Year", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Admission", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Applicant", + "link_count": 0, + "link_to": "Student Applicant", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Admission", + "link_count": 0, + "link_to": "Student Admission", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program Enrollment", + "link_count": 0, + "link_to": "Program Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Enrollment", + "link_count": 0, + "link_to": "Course Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Fees", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Structure", + "link_count": 0, + "link_to": "Fee Structure", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Category", + "link_count": 0, + "link_to": "Fee Category", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fee Schedule", + "link_count": 0, + "link_to": "Fee Schedule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Fees", + "link_count": 0, + "link_to": "Fees", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Fees", + "hidden": 0, + "is_query_report": 1, + "label": "Student Fee Collection Report", + "link_count": 0, + "link_to": "Student Fee Collection", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Fees", + "hidden": 0, + "is_query_report": 1, + "label": "Program wise Fee Collection Report", + "link_count": 0, + "link_to": "Program wise Fee Collection", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Schedule", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Schedule", + "link_count": 0, + "link_to": "Course Schedule", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Scheduling Tool", + "link_count": 0, + "link_to": "Course Scheduling Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Attendance", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Attendance", + "link_count": 0, + "link_to": "Student Attendance", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Leave Application", + "link_count": 0, + "link_to": "Student Leave Application", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Student Monthly Attendance Sheet", + "link_count": 0, + "link_to": "Student Monthly Attendance Sheet", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Absent Student Report", + "link_count": 0, + "link_to": "Absent Student Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Student Attendance", + "hidden": 0, + "is_query_report": 1, + "label": "Student Batch-Wise Attendance", + "link_count": 0, + "link_to": "Student Batch-Wise Attendance", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "LMS Activity", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Enrollment", + "link_count": 0, + "link_to": "Course Enrollment", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Activity", + "link_count": 0, + "link_to": "Course Activity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Quiz Activity", + "link_count": 0, + "link_to": "Quiz Activity", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Assessment", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Plan", + "link_count": 0, + "link_to": "Assessment Plan", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Group", + "link_count": 0, + "link_to": "Assessment Group", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Result", + "link_count": 0, + "link_to": "Assessment Result", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Criteria", + "link_count": 0, + "link_to": "Assessment Criteria", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Reports", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Assessment Result", + "hidden": 0, + "is_query_report": 1, + "label": "Course wise Assessment Report", + "link_count": 0, + "link_to": "Course wise Assessment Report", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Assessment Result", + "hidden": 0, + "is_query_report": 1, + "label": "Final Assessment Grades", + "link_count": 0, + "link_to": "Final Assessment Grades", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "Assessment Plan", + "hidden": 0, + "is_query_report": 1, + "label": "Assessment Plan Status", + "link_count": 0, + "link_to": "Assessment Plan Status", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Report Generation Tool", + "link_count": 0, + "link_to": "Student Report Generation Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Tools", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Attendance Tool", + "link_count": 0, + "link_to": "Student Attendance Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Assessment Result Tool", + "link_count": 0, + "link_to": "Assessment Result Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Student Group Creation Tool", + "link_count": 0, + "link_to": "Student Group Creation Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Program Enrollment Tool", + "link_count": 0, + "link_to": "Program Enrollment Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Course Scheduling Tool", + "link_count": 0, + "link_to": "Course Scheduling Tool", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other Reports", + "link_count": 0, + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "Program Enrollment", + "hidden": 0, + "is_query_report": 1, + "label": "Student and Guardian Contact Details", + "link_count": 0, + "link_to": "Student and Guardian Contact Details", + "link_type": "Report", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2022-01-13 17:29:13.676542", + "modified_by": "Administrator", + "module": "Education", + "name": "Education", + "owner": "Administrator", + "parent_page": "", + "public": 1, + "restrict_to_domain": "Education", + "roles": [], + "sequence_id": 9.0, + "shortcuts": [ + { + "color": "Grey", + "format": "{} Active", + "label": "Student", + "link_to": "Student", + "stats_filter": "{\n \"enabled\": 1\n}", + "type": "DocType" + }, + { + "color": "Grey", + "format": "{} Active", + "label": "Instructor", + "link_to": "Instructor", + "stats_filter": "{\n \"status\": \"Active\"\n}", + "type": "DocType" + }, + { + "color": "", + "format": "", + "label": "Program", + "link_to": "Program", + "stats_filter": "", + "type": "DocType" + }, + { + "label": "Course", + "link_to": "Course", + "type": "DocType" + }, + { + "color": "Grey", + "format": "{} Unpaid", + "label": "Fees", + "link_to": "Fees", + "stats_filter": "{\n \"outstanding_amount\": [\"!=\", 0.0]\n}", + "type": "DocType" + }, + { + "label": "Student Monthly Attendance Sheet", + "link_to": "Student Monthly Attendance Sheet", + "type": "Report" + }, + { + "label": "Course Scheduling Tool", + "link_to": "Course Scheduling Tool", + "type": "DocType" + }, + { + "label": "Student Attendance Tool", + "link_to": "Student Attendance Tool", + "type": "DocType" + }, + { + "label": "Dashboard", + "link_to": "Education", + "type": "Dashboard" + } + ], + "title": "Education" + } \ No newline at end of file diff --git a/education/hooks.py b/education/hooks.py new file mode 100644 index 0000000..8b551ea --- /dev/null +++ b/education/hooks.py @@ -0,0 +1,283 @@ +from frappe import _ + +from . import __version__ as app_version + +app_name = "education" +app_title = "Education" +app_publisher = "InfluxERP" +app_description = "Education" +app_icon = "octicon octicon-file-directory" +app_color = "grey" +app_email = "hello@frappe.io" +app_license = "GNU GPL V3" + +required_apps = ["erpnext"] + +# Includes in +# ------------------ + +# include js, css files in header of desk.html +# app_include_css = "/assets/education/css/education.css" +# app_include_js = "/assets/education/js/education.js" + +# include js, css files in header of web template +# web_include_css = "/assets/education/css/education.css" +# web_include_js = "/assets/education/js/education.js" + +# include custom scss in every website theme (without file extension ".scss") +# website_theme_scss = "education/public/scss/website" + +# website +update_website_context = [ + "education.education.doctype.education_settings.education_settings.update_website_context", +] + +website_generators = ["Student Admission"] + +website_route_rules = [ + {"from_route": "/admissions", "to_route": "Student Admission"}, +] + +treeviews = ["Assessment Group"] + +calendars = [ + "Course Schedule", +] + +standard_portal_menu_items = [ + {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role": "Student"}, + { + "title": _("Admission"), + "route": "/admissions", + "reference_doctype": "Student Admission", + "role": "Student", + }, +] + +default_roles = [ + {"role": "Student", "doctype": "Student", "email_field": "student_email_id"}, +] + +accounting_dimension_doctypes = ["Fee Schedule", "Fee Structure", "Fees"] + +global_search_doctypes = { + "Education": [ + {"doctype": "Article", "index": 1}, + {"doctype": "Video", "index": 2}, + {"doctype": "Topic", "index": 3}, + {"doctype": "Course", "index": 4}, + {"doctype": "Program", "index": 5}, + {"doctype": "Quiz", "index": 6}, + {"doctype": "Question", "index": 7}, + {"doctype": "Fee Schedule", "index": 8}, + {"doctype": "Fee Structure", "index": 9}, + {"doctype": "Fees", "index": 10}, + {"doctype": "Student Group", "index": 11}, + {"doctype": "Student", "index": 12}, + {"doctype": "Instructor", "index": 13}, + {"doctype": "Course Activity", "index": 14}, + {"doctype": "Quiz Activity", "index": 15}, + {"doctype": "Course Enrollment", "index": 16}, + {"doctype": "Program Enrollment", "index": 17}, + {"doctype": "Student Language", "index": 18}, + {"doctype": "Student Applicant", "index": 19}, + {"doctype": "Assessment Result", "index": 20}, + {"doctype": "Assessment Plan", "index": 21}, + {"doctype": "Grading Scale", "index": 22}, + {"doctype": "Guardian", "index": 23}, + {"doctype": "Student Leave Application", "index": 24}, + {"doctype": "Student Log", "index": 25}, + {"doctype": "Room", "index": 26}, + {"doctype": "Course Schedule", "index": 27}, + {"doctype": "Student Attendance", "index": 28}, + {"doctype": "Announcement", "index": 29}, + {"doctype": "Student Category", "index": 30}, + {"doctype": "Assessment Group", "index": 31}, + {"doctype": "Student Batch Name", "index": 32}, + {"doctype": "Assessment Criteria", "index": 33}, + {"doctype": "Academic Year", "index": 34}, + {"doctype": "Academic Term", "index": 35}, + {"doctype": "School House", "index": 36}, + {"doctype": "Student Admission", "index": 37}, + {"doctype": "Fee Category", "index": 38}, + {"doctype": "Assessment Code", "index": 39}, + {"doctype": "Discussion", "index": 40}, + ] +} + +# fixed route to education setup +domains = { + "Education": "education.education.setup", +} +# include js, css files in header of web form +# webform_include_js = {"doctype": "public/js/doctype.js"} +# webform_include_css = {"doctype": "public/css/doctype.css"} + +# include js in page +# page_js = {"page" : "public/js/file.js"} + +# include js in doctype views +# doctype_js = {"doctype" : "public/js/doctype.js"} +# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} +# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} +# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} + +# Home Pages +# ---------- + +# application home page (will override Website Settings) +# home_page = "login" + +# website user home page (by Role) +# role_home_page = { +# "Role": "home_page" +# } + +# Generators +# ---------- + +# automatically create page for each record of this doctype +# website_generators = ["Web Page"] + +# Jinja +# ---------- + +# add methods and filters to jinja environment +# jinja = { +# "methods": "education.utils.jinja_methods", +# "filters": "education.utils.jinja_filters" +# } + +# Installation +# ------------ + +# before_install = "education.install.before_install" +# after_install = "education.install.after_install" + +# Uninstallation +# ------------ + +# before_uninstall = "education.uninstall.before_uninstall" +# after_uninstall = "education.uninstall.after_uninstall" + +# Desk Notifications +# ------------------ +# See frappe.core.notifications.get_notification_config + +# notification_config = "education.notifications.get_notification_config" + +# Permissions +# ----------- +# Permissions evaluated in scripted ways + +# permission_query_conditions = { +# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", +# } +# +# has_permission = { +# "Event": "frappe.desk.doctype.event.event.has_permission", +# } + +# DocType Class +# --------------- +# Override standard doctype classes + +# override_doctype_class = { +# "ToDo": "custom_app.overrides.CustomToDo" +# } + +# Document Events +# --------------- +# Hook on document methods and events + +# doc_events = { +# "*": { +# "on_update": "method", +# "on_cancel": "method", +# "on_trash": "method" +# } +# } + +# Scheduled Tasks +# --------------- + +# scheduler_events = { +# "all": [ +# "education.tasks.all" +# ], +# "daily": [ +# "education.tasks.daily" +# ], +# "hourly": [ +# "education.tasks.hourly" +# ], +# "weekly": [ +# "education.tasks.weekly" +# ], +# "monthly": [ +# "education.tasks.monthly" +# ], +# } + +# Testing +# ------- + +# before_tests = "education.install.before_tests" + +# Overriding Methods +# ------------------------------ +# +# override_whitelisted_methods = { +# "frappe.desk.doctype.event.event.get_events": "education.event.get_events" +# } +# +# each overriding function accepts a `data` argument; +# generated from the base implementation of the doctype dashboard, +# along with any modifications made in other InfluxERP apps +# override_doctype_dashboards = { +# "Task": "education.task.get_dashboard_data" +# } + +# exempt linked doctypes from being automatically cancelled +# +# auto_cancel_exempted_doctypes = ["Auto Repeat"] + + +# User Data Protection +# -------------------- + +# user_data_fields = [ +# { +# "doctype": "{doctype_1}", +# "filter_by": "{filter_by}", +# "redact_fields": ["{field_1}", "{field_2}"], +# "partial": 1, +# }, +# { +# "doctype": "{doctype_2}", +# "filter_by": "{filter_by}", +# "partial": 1, +# }, +# { +# "doctype": "{doctype_3}", +# "strict": False, +# }, +# { +# "doctype": "{doctype_4}" +# } +# ] + +# Authentication and authorization +# -------------------------------- + +# auth_hooks = [ +# "education.auth.validate" +# ] + +# Translation +# -------------------------------- + +# Make link fields search translated document names for these DocTypes +# Recommended only for DocTypes which have limited documents with untranslated names +# For example: Role, Gender, etc. +# translated_search_doctypes = [] diff --git a/education/modules.txt b/education/modules.txt new file mode 100644 index 0000000..937e7cf --- /dev/null +++ b/education/modules.txt @@ -0,0 +1 @@ +Education \ No newline at end of file diff --git a/education/patches.txt b/education/patches.txt new file mode 100644 index 0000000..e69de29 diff --git a/education/public/.gitkeep b/education/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/education/public/js/assessment_result_tool.html b/education/public/js/assessment_result_tool.html new file mode 100644 index 0000000..f7d1ab3 --- /dev/null +++ b/education/public/js/assessment_result_tool.html @@ -0,0 +1,72 @@ + + + + + + {% for c in criteria %} + + {% endfor %} + + + + + + {% for c in criteria %} + + {% endfor %} + + + + + {% for s in students %} + + + + + {% for c in criteria %} + + {% endfor %} + + + {% endfor %} + +
StudentStudent Name{{ c.assessment_criteria }}CommentsTotal Marks
Score ({{ c.maximum_score }})Score ({{max_total_score}})
{{ s.student }}{{ s.student_name }} + + {% if(s.assessment_details) { %} + {{s.assessment_details[c.assessment_criteria][1]}} + {% } %} + + + + + + + {% if(s.assessment_details) { %} + {{s.assessment_details.total_score[1]}} + {% } %} + + + {% if(s.assessment_details) { %} + {{s.assessment_details.total_score[0]}} + {% } %} + + + + + + +
diff --git a/education/public/js/quiz.js b/education/public/js/quiz.js new file mode 100644 index 0000000..f79c121 --- /dev/null +++ b/education/public/js/quiz.js @@ -0,0 +1,238 @@ +class Quiz { + constructor(wrapper, options) { + this.wrapper = wrapper; + Object.assign(this, options); + this.questions = [] + this.refresh(); + } + + refresh() { + this.get_quiz(); + } + + get_quiz() { + frappe.call('education.education.utils.get_quiz', { + quiz_name: this.name, + course: this.course + }).then(res => { + this.make(res.message) + }); + } + + make(data) { + if (data.is_time_bound) { + $(".lms-timer").removeClass("hide"); + if (!data.activity || (data.activity && !data.activity.is_complete)) { + this.initialiseTimer(data.duration); + this.is_time_bound = true; + this.time_taken = 0; + } + } + data.questions.forEach(question_data => { + let question_wrapper = document.createElement('div'); + let question = new Question({ + wrapper: question_wrapper, + ...question_data + }); + this.questions.push(question) + this.wrapper.appendChild(question_wrapper); + }) + if (data.activity && data.activity.is_complete) { + this.disable() + let indicator = 'red' + let message = 'Your are not allowed to attempt the quiz again.' + if (data.activity.result == 'Pass') { + indicator = 'green' + message = 'You have already cleared the quiz.' + } + if (data.activity.time_taken) { + this.calculate_and_display_time(data.activity.time_taken, "Time Taken - "); + } + this.set_quiz_footer(message, indicator, data.activity.score) + } + else { + this.make_actions(); + } + window.addEventListener('beforeunload', (event) => { + event.preventDefault(); + event.returnValue = ''; + }); + } + + initialiseTimer(duration) { + this.time_left = duration; + var self = this; + var old_diff; + this.calculate_and_display_time(this.time_left, "Time Left - "); + this.start_time = new Date().getTime(); + this.timer = setInterval(function () { + var diff = (new Date().getTime() - self.start_time)/1000; + var variation = old_diff ? diff - old_diff : diff; + old_diff = diff; + self.time_left -= variation; + self.time_taken += variation; + self.calculate_and_display_time(self.time_left, "Time Left - "); + if (self.time_left <= 0) { + clearInterval(self.timer); + self.time_taken -= 1; + self.submit(); + } + }, 1000); + } + + calculate_and_display_time(second, text) { + var timer_display = document.getElementsByClassName("lms-timer")[0]; + var hours = this.append_zero(Math.floor(second / 3600)); + var minutes = this.append_zero(Math.floor(second % 3600 / 60)); + var seconds = this.append_zero(Math.ceil(second % 3600 % 60)); + timer_display.innerText = text + hours + ":" + minutes + ":" + seconds; + } + + append_zero(time) { + return time > 9 ? time : "0" + time; + } + + make_actions() { + const button = document.createElement("button"); + button.classList.add("btn", "btn-primary", "mt-5", "mr-2"); + + button.id = 'submit-button'; + button.innerText = 'Submit'; + button.onclick = () => this.submit(); + this.submit_btn = button + this.wrapper.appendChild(button); + } + + submit() { + if (this.is_time_bound) { + clearInterval(this.timer); + $(".lms-timer").text(""); + } + this.submit_btn.innerText = 'Evaluating..' + this.submit_btn.disabled = true + this.disable() + frappe.call('education.education.utils.evaluate_quiz', { + quiz_name: this.name, + quiz_response: this.get_selected(), + course: this.course, + program: this.program, + time_taken: this.is_time_bound ? this.time_taken : 0 + }).then(res => { + this.submit_btn.remove() + if (!res.message) { + frappe.throw(__("Something went wrong while evaluating the quiz.")) + } + + let indicator = 'red' + let message = 'Fail' + if (res.message.status == 'Pass') { + indicator = 'green' + message = 'Congratulations, you cleared the quiz.' + } + + this.set_quiz_footer(message, indicator, res.message.score) + }); + } + + set_quiz_footer(message, indicator, score) { + const div = document.createElement("div"); + div.classList.add("mt-5"); + div.innerHTML = `
+
+

${message}

+
Score: ${score}/100
+
+ +
` + + this.wrapper.appendChild(div) + } + + disable() { + this.questions.forEach(que => que.disable()) + } + + get_selected() { + let que = {} + this.questions.forEach(question => { + que[question.name] = question.get_selected() + }) + return que + } +} + +class Question { + constructor(opts) { + Object.assign(this, opts); + this.make(); + } + + make() { + this.make_question() + this.make_options() + } + + get_selected() { + let selected = this.options.filter(opt => opt.input.checked) + if (this.type == 'Single Correct Answer') { + if (selected[0]) return selected[0].name + } + if (this.type == 'Multiple Correct Answer') { + return selected.map(opt => opt.name) + } + return null + } + + disable() { + let selected = this.options.forEach(opt => opt.input.disabled = true) + } + + make_question() { + let question_wrapper = document.createElement('h5'); + question_wrapper.classList.add('mt-3'); + question_wrapper.innerHTML = this.question; + this.wrapper.appendChild(question_wrapper); + } + + make_options() { + let make_input = (name, value) => { + let input = document.createElement('input'); + input.id = name; + input.name = this.name; + input.value = value; + input.type = 'radio'; + if (this.type == 'Multiple Correct Answer') + input.type = 'checkbox'; + input.classList.add('form-check-input'); + return input; + } + + let make_label = function (name, value) { + let label = document.createElement('label'); + label.classList.add('form-check-label'); + label.htmlFor = name; + label.innerText = value; + return label + } + + let make_option = function (wrapper, option) { + let option_div = document.createElement('div'); + option_div.classList.add('form-check', 'pb-1'); + let input = make_input(option.name, option.option); + let label = make_label(option.name, option.option); + option_div.appendChild(input); + option_div.appendChild(label); + wrapper.appendChild(option_div); + return { input: input, ...option }; + } + + let options_wrapper = document.createElement('div') + options_wrapper.classList.add('ml-2') + let option_list = [] + this.options.forEach(opt => option_list.push(make_option(options_wrapper, opt))) + this.options = option_list + this.wrapper.appendChild(options_wrapper) + } +} diff --git a/education/templates/__init__.py b/education/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/templates/generators/student_admission.html b/education/templates/generators/student_admission.html new file mode 100644 index 0000000..8cc58a0 --- /dev/null +++ b/education/templates/generators/student_admission.html @@ -0,0 +1,27 @@ + +{% extends "templates/web.html" %} + +{% block breadcrumbs %} + {% include "templates/includes/breadcrumbs.html" %} +{% endblock %} + +{% block header %} + +

{{ title }}

+{% endblock %} + +{% block page_content %} + +{%- if introduction -%} +
{{ introduction }}
+{% endif %} + +{%- if doc.enable_admission_application -%} +

+ + {{ _("Apply Now") }} +

+{% endif %} + +{% endblock %} diff --git a/education/templates/includes/assessment/assessment_row.html b/education/templates/includes/assessment/assessment_row.html new file mode 100644 index 0000000..a33ccff --- /dev/null +++ b/education/templates/includes/assessment/assessment_row.html @@ -0,0 +1,19 @@ +
+
+
+ {{ doc.course }} +
+
+ {{ doc.room }} +
+
+ {{doc.schedule_date }} +
+
+ {{ doc.from_time }} +
+
+ {{ doc.to_time }} +
+
+
diff --git a/education/templates/includes/course/course_row.html b/education/templates/includes/course/course_row.html new file mode 100644 index 0000000..fddfc3c --- /dev/null +++ b/education/templates/includes/course/course_row.html @@ -0,0 +1,18 @@ + diff --git a/education/templates/includes/course/macros.html b/education/templates/includes/course/macros.html new file mode 100644 index 0000000..334b5ea --- /dev/null +++ b/education/templates/includes/course/macros.html @@ -0,0 +1 @@ +{% macro back_link(doc) %}&back-to=/courses?course={{ doc.name }}&back-to-title={{ doc.course_name }}{% endmacro %} diff --git a/education/templates/pages/__init__.py b/education/templates/pages/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/www/__init__.py b/education/www/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/www/lms/__init__.py b/education/www/lms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/www/lms/content.html b/education/www/lms/content.html new file mode 100644 index 0000000..930b494 --- /dev/null +++ b/education/www/lms/content.html @@ -0,0 +1,246 @@ +{% extends "templates/base.html" %} +{% block title %}{{ content.name or 'Content Page' }}{% endblock %} + +{% block head_include %} + + +{% endblock %} + +{% macro title() %} + +
+

{{ content.name }} ({{ position + 1 }}/{{length}})

+
+
+{% endmacro %} + +{% macro navigation() %} + {% if previous %} + {{_('Previous')}} + {% else %} + {{ _('Back to Course') }} + {% endif %} + + {% if next %} + + {% else %} + + {% endif %} +{% endmacro %} + +{% macro video() %} +
+ {{ title() }} +
+ {% if content.duration %} + {{ content.duration }} {{_('Mins')}} + {% endif %} + + {% if content.publish_date and content.duration%} + - + {% endif %} + + {% if content.publish_date %} + {{_('Published on')}} {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+
+ {{ content.description }} +
+{% endmacro %} + +{% macro article() %} +
+ {{ title() }} +
+ {% if content.author or content.publish_date %} + {{_('Published')}} + {% endif %} + {% if content.author %} + {{_('by')}} {{ content.author }} + {% endif %} + {% if content.publish_date %} + {{_('on')}} {{ content.publish_date.strftime('%d, %b %Y') }} + {% endif %} +
+
+
+ {{ content.content }} +
+{% endmacro %} + +{% macro quiz() %} +
+ {{ title() }} +
+
+
+{% endmacro %} + +{% block content %} +
+
+
+ {% if content_type=='Video' %} + {{ video() }} + {% elif content_type=='Article'%} + {{ article() }} + {% elif content_type=='Quiz' %} + {{ quiz() }} + {% endif %} +
+ {{ navigation() }} +
+
+
+
+{% endblock %} + +{% block script %} + {% if content_type=='Video' %} + + {% elif content_type == 'Quiz' %} + + {% endif %} + +{% endblock %} diff --git a/education/www/lms/content.py b/education/www/lms/content.py new file mode 100644 index 0000000..70d3a59 --- /dev/null +++ b/education/www/lms/content.py @@ -0,0 +1,78 @@ +import frappe + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + # Load Query Parameters + try: + program = frappe.form_dict["program"] + content = frappe.form_dict["content"] + content_type = frappe.form_dict["type"] + course = frappe.form_dict["course"] + topic = frappe.form_dict["topic"] + except KeyError: + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + # Check if user has access to the content + has_program_access = utils.allowed_program_access(program) + has_content_access = allowed_content_access(program, content, content_type) + + if frappe.session.user == "Guest" or not has_program_access or not has_content_access: + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + # Set context for content to be displayer + context.content = frappe.get_doc(content_type, content).as_dict() + context.content_type = content_type + context.program = program + context.course = course + context.topic = topic + + topic = frappe.get_doc("Topic", topic) + content_list = [ + {"content_type": item.content_type, "content": item.content} + for item in topic.topic_content + ] + + # Set context for progress numbers + context.position = content_list.index( + {"content": content, "content_type": content_type} + ) + context.length = len(content_list) + + # Set context for navigation + context.previous = get_previous_content(content_list, context.position) + context.next = get_next_content(content_list, context.position) + + +def get_next_content(content_list, current_index): + try: + return content_list[current_index + 1] + except IndexError: + return None + + +def get_previous_content(content_list, current_index): + if current_index == 0: + return None + else: + return content_list[current_index - 1] + + +def allowed_content_access(program, content, content_type): + contents_of_program = frappe.db.sql( + """select `tabTopic Content`.content, `tabTopic Content`.content_type + from `tabCourse Topic`, + `tabProgram Course`, + `tabTopic Content` + where `tabCourse Topic`.parent = `tabProgram Course`.course + and `tabTopic Content`.parent = `tabCourse Topic`.topic + and `tabProgram Course`.parent = %(program)s""", + {"program": program}, + ) + + return (content, content_type) in contents_of_program diff --git a/education/www/lms/course.html b/education/www/lms/course.html new file mode 100644 index 0000000..c07b940 --- /dev/null +++ b/education/www/lms/course.html @@ -0,0 +1,106 @@ +{% extends "templates/base.html" %} +{% block title %}{{ course.course_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(topic) %} +
+
+ {% if has_access %} + + {% else %} + + {% endif %} + {% if topic.hero_image %} +
+ {% else %} +
+
+
+ {% endif %} +
+ {% if has_access %} + + + {% else %} + + {% endif %} +
+
+{% endmacro %} + +{% block content %} +
+ {{ hero(course.course_name, course.description, has_access, {'name': 'Program', 'url': '/lms/program?program=' + program }) }} +
+
+ {% for topic in topics %} + {{ card(topic) }} + {% endfor %} + {% if topics %} + {% for n in range( (3 - (topics|length)) %3) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} diff --git a/education/www/lms/course.py b/education/www/lms/course.py new file mode 100644 index 0000000..d9241b7 --- /dev/null +++ b/education/www/lms/course.py @@ -0,0 +1,30 @@ +import frappe + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + try: + program = frappe.form_dict["program"] + course_name = frappe.form_dict["name"] + except KeyError: + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + context.education_settings = frappe.get_single("Education Settings") + course = frappe.get_doc("Course", course_name) + context.program = program + context.course = course + + context.topics = course.get_topics() + context.has_access = utils.allowed_program_access(context.program) + context.progress = get_topic_progress(context.topics, course, context.program) + + +def get_topic_progress(topics, course, program): + progress = { + topic.name: utils.get_topic_progress(topic, course.name, program) for topic in topics + } + return progress diff --git a/education/www/lms/index.html b/education/www/lms/index.html new file mode 100644 index 0000000..c1e9620 --- /dev/null +++ b/education/www/lms/index.html @@ -0,0 +1,69 @@ +{% extends "templates/base.html" %} +{% block title %}{{ education_settings.portal_title }}{% endblock %} +{% from "www/lms/macros/card.html" import program_card %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + + + +{% endblock %} + +{% block content %} +
+
+

{{ education_settings.portal_title }}

+ {% if education_settings.description %} +

{{ education_settings.description }}

+ {% endif %} +

+ {% if frappe.session.user == 'Guest' %} + {{_('Sign Up')}} + {% endif %} +

+
+
+
+ {% if featured_programs %} + {% for program in featured_programs %} + {{ program_card(program.program, program.has_access) }} + {% endfor %} + {% for n in range( (3 - (featured_programs|length)) %3) %} + {{ null_card() }} + {% endfor %} + {% else %} +

You have not enrolled in any program. Contact your Instructor.

+ {% endif %} +
+
+
+{% endblock %} diff --git a/education/www/lms/index.py b/education/www/lms/index.py new file mode 100644 index 0000000..bf82fcc --- /dev/null +++ b/education/www/lms/index.py @@ -0,0 +1,17 @@ +import frappe + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + context.education_settings = frappe.get_single("Education Settings") + if not context.education_settings.enable_lms: + frappe.local.flags.redirect_location = "/" + raise frappe.Redirect + context.featured_programs = get_featured_programs() + + +def get_featured_programs(): + return utils.get_portal_programs() or [] diff --git a/education/www/lms/macros/__init__.py b/education/www/lms/macros/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/www/lms/macros/card.html b/education/www/lms/macros/card.html new file mode 100644 index 0000000..3cbdec6 --- /dev/null +++ b/education/www/lms/macros/card.html @@ -0,0 +1,34 @@ +{% macro program_card(program, has_access) %} + +{% endmacro %} + + +{% macro null_card() %} +
+
+
+
+{% endmacro %} diff --git a/education/www/lms/macros/hero.html b/education/www/lms/macros/hero.html new file mode 100644 index 0000000..b47d507 --- /dev/null +++ b/education/www/lms/macros/hero.html @@ -0,0 +1,53 @@ +{% macro hero(title, description, has_access, back) %} +
+ +

{{ title }}

+

{{ description or ''}}

+

+ {% if frappe.session.user == 'Guest' %} + {{_('Sign Up')}} + {% elif not has_access %} + + {% endif %} +

+
+ +{% block script %} + +{% endblock %} +{% endmacro %} diff --git a/education/www/lms/profile.html b/education/www/lms/profile.html new file mode 100644 index 0000000..5755dfe --- /dev/null +++ b/education/www/lms/profile.html @@ -0,0 +1,64 @@ +{% extends "templates/base.html" %} +{% block title %}Profile{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} + +{% block head_include %} + +{% endblock %} + +{% macro card(program) %} + +{% endmacro %} + +{% block content %} +
+
+ +

{{ student.first_name }} {{ student.last_name or '' }}

+

{{ student.name }}

+
+
+
+ {% for program in progress %} + {{ card(program) }} + {% endfor %} +
+
+
+{% endblock %} diff --git a/education/www/lms/profile.py b/education/www/lms/profile.py new file mode 100644 index 0000000..2e7f5ea --- /dev/null +++ b/education/www/lms/profile.py @@ -0,0 +1,37 @@ +import frappe + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + if frappe.session.user == "Guest": + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + context.student = utils.get_current_student() + if not context.student: + context.student = frappe.get_doc("User", frappe.session.user) + context.progress = get_program_progress(context.student.name) + + +def get_program_progress(student): + enrolled_programs = frappe.get_all( + "Program Enrollment", filters={"student": student}, fields=["program"] + ) + student_progress = [] + for list_item in enrolled_programs: + program = frappe.get_doc("Program", list_item.program) + progress = utils.get_program_progress(program) + completion = utils.get_program_completion(program) + student_progress.append( + { + "program": program.program_name, + "name": program.name, + "progress": progress, + "completion": completion, + } + ) + + return student_progress diff --git a/education/www/lms/program.html b/education/www/lms/program.html new file mode 100644 index 0000000..30528c6 --- /dev/null +++ b/education/www/lms/program.html @@ -0,0 +1,87 @@ +{% extends "templates/base.html" %} +{% block title %}{{ program.program_name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(course) %} + +{% endmacro %} + +{% block content %} +
+ {{ hero(program.program_name, program.description, has_access, {'name': 'Home', 'url': '/lms'}) }} +
+
+ {% for course in courses %} + {{ card(course) }} + {% endfor %} + {% if courses %} + {% for n in range( (3 - (courses|length)) %3) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} diff --git a/education/www/lms/program.py b/education/www/lms/program.py new file mode 100644 index 0000000..330838e --- /dev/null +++ b/education/www/lms/program.py @@ -0,0 +1,36 @@ +import frappe +from frappe import _ + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + try: + program = frappe.form_dict["program"] + except KeyError: + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + context.education_settings = frappe.get_single("Education Settings") + context.program = get_program(program) + context.courses = [ + frappe.get_doc("Course", course.course) for course in context.program.courses + ] + context.has_access = utils.allowed_program_access(program) + context.progress = get_course_progress(context.courses, context.program) + + +def get_program(program_name): + try: + return frappe.get_doc("Program", program_name) + except frappe.DoesNotExistError: + frappe.throw(_("Program {0} does not exist.").format(program_name)) + + +def get_course_progress(courses, program): + progress = { + course.name: utils.get_course_progress(course, program) for course in courses + } + return progress or {} diff --git a/education/www/lms/topic.html b/education/www/lms/topic.html new file mode 100644 index 0000000..dc69599 --- /dev/null +++ b/education/www/lms/topic.html @@ -0,0 +1,58 @@ +{% extends "templates/base.html" %} +{% block title %}{{ topic.name }}{% endblock %} +{% from "www/lms/macros/hero.html" import hero %} +{% from "www/lms/macros/card.html" import null_card %} + +{% block head_include %} + +{% endblock %} + + +{% macro card(content, index, length) %} + +{% endmacro %} + +{% block content %} +
+ {{ hero(topic.topic_name, topic.description, has_access, {'name': 'Course', 'url': '/lms/course?name=' + course +'&program=' + program}) }} +
+
+ {% for content in contents %} + {{ card(content, loop.index, topic.contents|length) }} + {% endfor %} + {% if contents %} + {% for n in range( (3 - (contents|length)) %3) %} + {{ null_card() }} + {% endfor %} + {% endif %} +
+
+
+{% endblock %} diff --git a/education/www/lms/topic.py b/education/www/lms/topic.py new file mode 100644 index 0000000..048fdd1 --- /dev/null +++ b/education/www/lms/topic.py @@ -0,0 +1,61 @@ +import frappe + +import education.education.utils as utils + +no_cache = 1 + + +def get_context(context): + try: + course = frappe.form_dict["course"] + program = frappe.form_dict["program"] + topic = frappe.form_dict["topic"] + except KeyError: + frappe.local.flags.redirect_location = "/lms" + raise frappe.Redirect + + context.program = program + context.course = course + context.topic = frappe.get_doc("Topic", topic) + context.contents = get_contents(context.topic, course, program) + context.has_access = utils.allowed_program_access(program) + + +def get_contents(topic, course, program): + student = utils.get_current_student() + if student: + course_enrollment = utils.get_or_create_course_enrollment(course, program) + contents = topic.get_contents() + progress = [] + if contents: + for content in contents: + if content.doctype in ("Article", "Video"): + if student: + status = utils.check_content_completion( + content.name, content.doctype, course_enrollment.name + ) + else: + status = True + progress.append( + {"content": content, "content_type": content.doctype, "completed": status} + ) + elif content.doctype == "Quiz": + if student: + status, score, result, time_taken = utils.check_quiz_completion( + content, course_enrollment.name + ) + else: + status = False + score = None + result = None + progress.append( + { + "content": content, + "content_type": content.doctype, + "completed": status, + "score": score, + "result": result, + } + ) + + return progress diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..beca179 --- /dev/null +++ b/license.txt @@ -0,0 +1 @@ +License: GNU GPL V3 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7668191 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +# frappe -- https://github.com/frappe/frappe is installed via 'bench init' \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..78bcde2 --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, find_packages + +with open("requirements.txt") as f: + install_requires = f.read().strip().split("\n") + +# get version from __version__ variable in education/__init__.py +from education import __version__ as version + +setup( + name="education", + version=version, + description="Education", + author="InfluxERP", + author_email="hello@frappe.io", + packages=find_packages(), + zip_safe=False, + include_package_data=True, + install_requires=install_requires, +)