diff --git a/.github/helper/install.sh b/.github/helper/install.sh index f3aa9d04..8ce74e3e 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -40,12 +40,15 @@ sed -i 's/schedule:/# schedule:/g' Procfile sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile +bench get-app payments --branch ${BRANCH_TO_CLONE%"-hotfix"} bench get-app https://github.com/frappe/erpnext --branch "$BRANCH_TO_CLONE" --resolve-deps bench get-app education "${GITHUB_WORKSPACE}" bench setup requirements --dev bench build bench start &>> ~/frappe-bench/bench_start.log & +CI=Yes bench build --app frappe & bench --site test_site reinstall --yes +bench --verbose --site test_site install-app payments bench --verbose --site test_site install-app education \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e1cdbdc..f8f7f08c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ on: # Run everday at midnight UTC / 5:30 IST - cron: "0 0 * * *" env: - EDUCATION_BRANCH: ${{ github.base_ref || github.ref_name }} + EDUCATION_BRANCH: ${{ github.base_ref || github.ref_name }} concurrency: group: develop-${{ github.event.number }} @@ -28,16 +28,13 @@ jobs: strategy: fail-fast: false - matrix: - container: [1, 2] - name: Python Unit Tests services: mysql: image: mariadb:10.6 env: - MARIADB_ROOT_PASSWORD: 'root' + MARIADB_ROOT_PASSWORD: "root" ports: - 3306:3306 options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 @@ -49,7 +46,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: "3.10" - name: Check for valid Python & Merge Conflicts run: | @@ -77,9 +74,38 @@ jobs: ${{ runner.os }}-pip- ${{ runner.os }}- + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Install run: | bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: BRANCH_TO_CLONE: ${{ env.EDUCATION_BRANCH }} + - name: Run Tests + run: cd ~/frappe-bench/ && bench --site test_site run-tests --app education + env: + TYPE: server + CI_BUILD_ID: ${{ github.run_id }} diff --git a/education/__init__.py b/education/__init__.py index f59bc580..1609d49b 100644 --- a/education/__init__.py +++ b/education/__init__.py @@ -1 +1 @@ -__version__ = "15.2.1" +__version__ = "15.3.0" diff --git a/education/education/doctype/academic_term/test_academic_term.py b/education/education/doctype/academic_term/test_academic_term.py index b4516b3a..b868aac2 100644 --- a/education/education/doctype/academic_term/test_academic_term.py +++ b/education/education/doctype/academic_term/test_academic_term.py @@ -1,10 +1,10 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Academic Term') -class TestAcademicTerm(unittest.TestCase): +class TestAcademicTerm(FrappeTestCase): pass diff --git a/education/education/doctype/academic_term/test_records.json b/education/education/doctype/academic_term/test_records.json deleted file mode 100644 index 6bd36556..00000000 --- a/education/education/doctype/academic_term/test_records.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "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/test_academic_year.py b/education/education/doctype/academic_year/test_academic_year.py index 6bd2753a..a9ed8d9d 100644 --- a/education/education/doctype/academic_year/test_academic_year.py +++ b/education/education/doctype/academic_year/test_academic_year.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest import frappe +from frappe.tests.utils import FrappeTestCase -class TestAcademicYear(unittest.TestCase): +class TestAcademicYear(FrappeTestCase): def test_date_validation(self): year = frappe.get_doc( { diff --git a/education/education/doctype/academic_year/test_records.json b/education/education/doctype/academic_year/test_records.json deleted file mode 100644 index 5eb5e2e3..00000000 --- a/education/education/doctype/academic_year/test_records.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "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/test_article.py b/education/education/doctype/article/test_article.py index 2ea5c82a..fe295993 100644 --- a/education/education/doctype/article/test_article.py +++ b/education/education/doctype/article/test_article.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestArticle(unittest.TestCase): + +class TestArticle(FrappeTestCase): pass diff --git a/education/education/doctype/assessment_criteria/test_assessment_criteria.py b/education/education/doctype/assessment_criteria/test_assessment_criteria.py index 90ff5754..6e009316 100644 --- a/education/education/doctype/assessment_criteria/test_assessment_criteria.py +++ b/education/education/doctype/assessment_criteria/test_assessment_criteria.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Assessment Criteria') -class TestAssessmentCriteria(unittest.TestCase): +class TestAssessmentCriteria(FrappeTestCase): pass diff --git a/education/education/doctype/assessment_criteria/test_records.json b/education/education/doctype/assessment_criteria/test_records.json index 7af63b39..a0ffa9ae 100644 --- a/education/education/doctype/assessment_criteria/test_records.json +++ b/education/education/doctype/assessment_criteria/test_records.json @@ -1,8 +1,8 @@ [ - { - "assessment_criteria": "_Test Assessment Criteria" - }, - { - "assessment_criteria": "_Test Assessment Criteria 1" - } -] \ No newline at end of file + { + "assessment_criteria": "_Test Assessment Criteria" + }, + { + "assessment_criteria": "_Test Assessment Criteria 1" + } +] 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 index b52aef83..72240ad4 100644 --- a/education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py +++ b/education/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Assessment Criteria Group') -class TestAssessmentCriteriaGroup(unittest.TestCase): +class TestAssessmentCriteriaGroup(FrappeTestCase): pass diff --git a/education/education/doctype/assessment_group/test_assessment_group.py b/education/education/doctype/assessment_group/test_assessment_group.py index 6f05caae..b6761c10 100644 --- a/education/education/doctype/assessment_group/test_assessment_group.py +++ b/education/education/doctype/assessment_group/test_assessment_group.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Assessment Group') -class TestAssessmentGroup(unittest.TestCase): +class TestAssessmentGroup(FrappeTestCase): pass diff --git a/education/education/doctype/assessment_plan/test_assessment_plan.py b/education/education/doctype/assessment_plan/test_assessment_plan.py index e294c50c..7453f139 100644 --- a/education/education/doctype/assessment_plan/test_assessment_plan.py +++ b/education/education/doctype/assessment_plan/test_assessment_plan.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Assessment Plan') -class TestAssessmentPlan(unittest.TestCase): +class TestAssessmentPlan(FrappeTestCase): pass diff --git a/education/education/doctype/assessment_result/test_assessment_result.py b/education/education/doctype/assessment_result/test_assessment_result.py index d9a39cd8..05e33285 100644 --- a/education/education/doctype/assessment_result/test_assessment_result.py +++ b/education/education/doctype/assessment_result/test_assessment_result.py @@ -1,14 +1,22 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +import frappe +from frappe.tests.utils import FrappeTestCase from education.education.api import get_grade +from education.education.test_utils import create_grading_scale # test_records = frappe.get_test_records('Assessment Result') -class TestAssessmentResult(unittest.TestCase): +class TestAssessmentResult(FrappeTestCase): + def setUp(self): + create_grading_scale() + + def tearDown(self): + frappe.db.rollback() + def test_grade(self): grade = get_grade("_Test Grading Scale", 80) self.assertEqual("A", grade) 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 index 49e0be08..d616cffc 100644 --- a/education/education/doctype/assessment_result_tool/test_assessment_result_tool.py +++ b/education/education/doctype/assessment_result_tool/test_assessment_result_tool.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestAssessmentResultTool(unittest.TestCase): + +class TestAssessmentResultTool(FrappeTestCase): pass diff --git a/education/education/doctype/course/test_course.py b/education/education/doctype/course/test_course.py index b6d4a3f4..86182e46 100644 --- a/education/education/doctype/course/test_course.py +++ b/education/education/doctype/course/test_course.py @@ -1,17 +1,19 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest import frappe from education.education.doctype.topic.test_topic import ( - make_topic, make_topic_and_linked_content) + make_topic, + make_topic_and_linked_content, +) +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Course') -class TestCourse(unittest.TestCase): +class TestCourse(FrappeTestCase): def setUp(self): make_topic_and_linked_content( "_Test Topic 1", [{"type": "Article", "name": "_Test Article 1"}] diff --git a/education/education/doctype/course_activity/test_course_activity.py b/education/education/doctype/course_activity/test_course_activity.py index 677b60a8..5a1129bc 100644 --- a/education/education/doctype/course_activity/test_course_activity.py +++ b/education/education/doctype/course_activity/test_course_activity.py @@ -1,12 +1,13 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase import frappe -class TestCourseActivity(unittest.TestCase): +class TestCourseActivity(FrappeTestCase): pass diff --git a/education/education/doctype/course_enrollment/test_course_enrollment.py b/education/education/doctype/course_enrollment/test_course_enrollment.py index 680f5a10..a2e7ac5b 100644 --- a/education/education/doctype/course_enrollment/test_course_enrollment.py +++ b/education/education/doctype/course_enrollment/test_course_enrollment.py @@ -1,67 +1,11 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # 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) +from frappe.tests.utils import FrappeTestCase -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() +class TestCourseEnrollment(FrappeTestCase): + pass diff --git a/education/education/doctype/course_schedule/test_course_schedule.py b/education/education/doctype/course_schedule/test_course_schedule.py index 95c3770e..1e416dc6 100644 --- a/education/education/doctype/course_schedule/test_course_schedule.py +++ b/education/education/doctype/course_schedule/test_course_schedule.py @@ -2,68 +2,104 @@ # 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 frappe.tests.utils import FrappeTestCase from education.education.utils import OverlapError +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_program, + create_student, + create_program_enrollment, + create_student_group, + create_instructor, + create_course, + create_room, +) + + +class TestCourseSchedule(FrappeTestCase): + def setUp(self): + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" + ) + create_program("Class 1") + student = create_student() + create_program_enrollment(student_name=student.name, submit=1) + create_student_group(student_group_name="Test Student Group") + create_student_group(student_group_name="Test Student Group 2") -# test_records = frappe.get_test_records('Course Schedule') + create_instructor() + create_instructor("Test Instructor 2") + create_course() + create_course("Test Course 2") -class TestCourseSchedule(unittest.TestCase): - def test_student_group_conflict(self): - cs1 = make_course_schedule_test_record(simulate=True) + create_room() + create_room("Test Room 2") + + def tearDown(self): + frappe.db.rollback() + def test_student_group_conflict(self): + cs1 = make_course_schedule_test_record(simulate=True, schedule_date="2023-08-01") 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", + 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) + cs1 = make_course_schedule_test_record(simulate=True, schedule_date="2023-08-01") cs2 = make_course_schedule_test_record( + schedule_date=cs1.schedule_date, from_time=cs1.from_time, to_time=cs1.to_time, - student_group="Course-TC101-2014-2015 (_Test Academic Term)", + student_group="Test Student Group 2", 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) + cs1 = make_course_schedule_test_record(simulate=True, schedule_date="2023-08-01") cs2 = make_course_schedule_test_record( + schedule_date=cs1.schedule_date, from_time=cs1.from_time, to_time=cs1.to_time, - student_group="Course-TC101-2014-2015 (_Test Academic Term)", - instructor="_Test Instructor 2", + student_group="Test Student Group 2", + 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) + cs1 = make_course_schedule_test_record(simulate=True, schedule_date="2023-08-01") make_course_schedule_test_record( + schedule_date=cs1.schedule_date, from_time=cs1.from_time, to_time=cs1.to_time, - student_group="Course-TC102-2014-2015 (_Test Academic Term)", - instructor="_Test Instructor 2", + student_group="Test Student Group 2", + 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 = make_course_schedule_test_record( + schedule_date=add_to_date("2023-08-01", days=1) + ) doc.schedule_date = add_to_date(doc.schedule_date, days=1) doc.save() @@ -72,11 +108,9 @@ 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.student_group = args.student_group or "Test Student Group" + course_schedule.course = args.course or "Test Course" + 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() 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 index 559214bc..004fcec3 100644 --- a/education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py +++ b/education/education/doctype/course_scheduling_tool/test_course_scheduling_tool.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestCourseSchedulingTool(unittest.TestCase): + +class TestCourseSchedulingTool(FrappeTestCase): pass diff --git a/education/education/doctype/course_topic/test_course_topic.py b/education/education/doctype/course_topic/test_course_topic.py index a4d370c8..9d5cbc72 100644 --- a/education/education/doctype/course_topic/test_course_topic.py +++ b/education/education/doctype/course_topic/test_course_topic.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestCourseTopic(unittest.TestCase): + +class TestCourseTopic(FrappeTestCase): pass diff --git a/education/education/doctype/education_settings/education_settings.json b/education/education/doctype/education_settings/education_settings.json index 99a48610..7334c185 100644 --- a/education/education/doctype/education_settings/education_settings.json +++ b/education/education/doctype/education_settings/education_settings.json @@ -13,8 +13,13 @@ "validate_course", "academic_term_reqd", "user_creation_skip", + "accounting_section", + "create_so", + "column_break_aimg", "auto_submit_sales_invoice", "sales_invoice_posting_date_fee_schedule", + "auto_submit_sales_order", + "sales_order_transaction_date_fee_schedule", "section_break_7", "instructor_created_by", "portal_settings_tab", @@ -88,6 +93,7 @@ }, { "default": "0", + "depends_on": "eval: !doc.create_so", "description": "By default, the Sales Invoice Created will be in Draft Mode. If enabled, the Sales Invoice will be submitted once created.", "fieldname": "auto_submit_sales_invoice", "fieldtype": "Check", @@ -131,15 +137,48 @@ }, { "default": "1", - "description": "By default, the Sales Invoice's Posting Date will be equal to Fee Schedule's Posting Date. If disabled then Sales Invoice,s Posting Date will be today's date.", + "depends_on": "eval: !doc.create_so", + "description": "By default, the Sales Invoice's Posting Date will be equal to Fee Schedule's Posting Date. If disabled then Sales Invoice's Posting Date will be today's date.", "fieldname": "sales_invoice_posting_date_fee_schedule", "fieldtype": "Check", "label": "Sales Invoice Posting Date should be same as Fee Schedule Posting Date" + }, + { + "fieldname": "accounting_section", + "fieldtype": "Section Break", + "label": "Accounting" + }, + { + "fieldname": "column_break_aimg", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "By default, Sales Invoice will be created against Program Enrollment / Fee Schedule.\nIf enabled Sales Order will be created", + "fieldname": "create_so", + "fieldtype": "Check", + "label": "Create Sales Order instead of Sales Invoice" + }, + { + "default": "0", + "depends_on": "eval: doc.create_so", + "description": "By default, the Sales Order Created will be in Draft Mode. If enabled, the Sales Order will be submitted once created.", + "fieldname": "auto_submit_sales_order", + "fieldtype": "Check", + "label": "Submit Sales Order from Program Enrollment / Fee Schedule" + }, + { + "default": "1", + "depends_on": "eval: doc.create_so", + "description": "By default, the Sales Order's Transaction Date will be equal to Fee Schedule's Transaction Date. If disabled then Sales Order's Transaction Date will be today's date.", + "fieldname": "sales_order_transaction_date_fee_schedule", + "fieldtype": "Check", + "label": "Sales Order Posting Date should be same as Fee Schedule Posting Date" } ], "issingle": 1, "links": [], - "modified": "2024-02-20 17:41:45.721252", + "modified": "2024-03-27 15:05:41.058873", "modified_by": "Administrator", "module": "Education", "name": "Education Settings", diff --git a/education/education/doctype/education_settings/test_education_settings.py b/education/education/doctype/education_settings/test_education_settings.py index 223e8386..29d981e2 100644 --- a/education/education/doctype/education_settings/test_education_settings.py +++ b/education/education/doctype/education_settings/test_education_settings.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestEducationSettings(unittest.TestCase): + +class TestEducationSettings(FrappeTestCase): pass diff --git a/education/education/doctype/fee_category/test_fee_category.py b/education/education/doctype/fee_category/test_fee_category.py index 93565a9f..f2b4b7f3 100644 --- a/education/education/doctype/fee_category/test_fee_category.py +++ b/education/education/doctype/fee_category/test_fee_category.py @@ -1,10 +1,8 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -# test_records = frappe.get_test_records('Fee Category') - -class TestFeeCategory(unittest.TestCase): +class TestFeeCategory(FrappeTestCase): pass diff --git a/education/education/doctype/fee_schedule/fee_schedule.js b/education/education/doctype/fee_schedule/fee_schedule.js index ae137855..e07fb560 100644 --- a/education/education/doctype/fee_schedule/fee_schedule.js +++ b/education/education/doctype/fee_schedule/fee_schedule.js @@ -2,61 +2,65 @@ // For license information, please see license.txt frappe.provide("erpnext.accounts.dimensions"); -frappe.ui.form.on('Fee Schedule', { - - company: function(frm) { +frappe.ui.form.on("Fee Schedule", { + company: function (frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, - onload: function(frm) { - frm.set_query('receivable_account', function(doc) { + + onload: function (frm) { + frm.set_query("receivable_account", function (doc) { return { filters: { - 'account_type': 'Receivable', - 'is_group': 0, - 'company': doc.company - } + account_type: "Receivable", + is_group: 0, + company: doc.company, + }, }; }); - frm.set_query('income_account', function(doc) { + frm.set_query("income_account", function (doc) { return { filters: { - 'account_type': 'Income Account', - 'is_group': 0, - 'company': doc.company - } + account_type: "Income Account", + is_group: 0, + company: doc.company, + }, }; }); - frm.set_query('academic_term', function(doc) { + frm.set_query("academic_term", function (doc) { return { filters: { - 'academic_year': doc.academic_year - } + academic_year: doc.academic_year, + }, }; }); - frm.set_query('student_group', 'student_groups', function() { + frm.set_query("student_group", "student_groups", function () { return { filters: { - 'program': frm.doc.program, - 'academic_term': frm.doc.academic_term, - 'academic_year': frm.doc.academic_year, - 'disabled': 0 - } + 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) { + 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'); + 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+'%'); + $(progress_bar) + .removeClass("progress-bar-danger") + .addClass("progress-bar-success progress-bar-striped"); + $(progress_bar).css("width", data.progress + "%"); } } }); @@ -64,75 +68,94 @@ frappe.ui.form.on('Fee Schedule', { 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') { + refresh: function (frm) { + if ( + !frm.doc.__islocal && + frm.doc.__onload && + frm.doc.__onload.dashboard_info && + (frm.doc.status === "Order Created" || frm.doc.status === "Invoice Created") + ) { 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'); + 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.docstatus === 1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status === 'Failed') { - frm.add_custom_button(__('Create Sales Invoice'), function() { - frappe.call({ - method: 'create_fees', - doc: frm.doc, - callback: function() { - frm.refresh(); - } - }); - }); + if (frm.doc.status === "In Process") { + frm.dashboard.add_progress("Fee Creation Status", "0"); } - if (frm.doc.fee_creation_status === 'Successful') { - frm.add_custom_button(__('View Sales Invoice Records'), function() { - frappe.route_options = { - fee_schedule: frm.doc.name - }; - frappe.set_route('List', 'Sales Invoice'); + if ( + (frm.doc.docstatus === 1) || + frm.doc.status === "Failed" + ) { + let button_label = "Create Sales Invoice"; + + frappe.db.get_value("Education Settings", {}, "create_so", (r) => { + // convert r.create_so to number + if (+r.create_so) { + button_label = "Create Sales Order"; + // set indicator in the frm + } + if (frm.doc.status === "Order Pending" || frm.doc.status === "Invoice Pending") { + frm.add_custom_button(__(button_label), function () { + frappe.call({ + method: "create_fees", + doc: frm.doc, + callback: function () { + frm.refresh(); + }, + }); + }); + } }); } - }, - fee_structure: function(frm) { + fee_structure: function (frm) { if (frm.doc.fee_structure) { frappe.call({ - method: 'education.education.doctype.fee_schedule.fee_schedule.get_fee_structure', + method: + "education.education.doctype.fee_schedule.fee_schedule.get_fee_structure", args: { - 'target_doc': frm.doc.name, - 'source_name': frm.doc.fee_structure + target_doc: frm.doc.name, + source_name: frm.doc.fee_structure, }, - callback: function(r) { + callback: function (r) { var doc = frappe.model.sync(r.message); - frappe.set_route('Form', doc[0].doctype, doc[0].name); - } + frappe.set_route("Form", doc[0].doctype, doc[0].name); + }, }); } - } + }, }); -frappe.ui.form.on('Fee Schedule Student Group', { - student_group: function(frm, cdt, cdn) { +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', + 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 + 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) { + callback: function (r) { if (r.message) { - frappe.model.set_value(cdt, cdn, 'total_students', r.message); + 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 index b8cc6d16..349fbba5 100644 --- a/education/education/doctype/fee_schedule/fee_schedule.json +++ b/education/education/doctype/fee_schedule/fee_schedule.json @@ -1,337 +1,370 @@ { - "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", - "label": "Components", - "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" - }, - { - "fetch_from": "fee_structure.receivable_account", - "fieldname": "receivable_account", - "fieldtype": "Link", - "label": "Receivable Account", - "options": "Account" - }, - { - "fetch_from": "fee_structure.income_account", - "fieldname": "income_account", - "fieldtype": "Link", - "label": "Income Account", - "options": "Account" - }, - { - "fieldname": "column_break_39", - "fieldtype": "Column Break" - }, - { - "fetch_from": "fee_structure.cost_center", - "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": "Small Text", - "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": [ - { - "link_doctype": "Sales Invoice", - "link_fieldname": "fee_schedule" - } - ], - "modified": "2024-01-23 12:18:14.075608", - "modified_by": "Administrator", - "module": "Education", - "name": "Fee Schedule", - "naming_rule": "By \"Naming Series\" field", - "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 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "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", + "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", + "status" + ], + "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": "status", + "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 30, + "no_copy": 1, + "options": "Draft\nCancelled\nInvoice Pending\nOrder Pending\nIn Process\nInvoice Created\nOrder Created\nFailed", + "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", + "label": "Components", + "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" + }, + { + "fetch_from": "fee_structure.receivable_account", + "fieldname": "receivable_account", + "fieldtype": "Link", + "label": "Receivable Account", + "options": "Account" + }, + { + "fetch_from": "fee_structure.income_account", + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "column_break_39", + "fieldtype": "Column Break" + }, + { + "fetch_from": "fee_structure.cost_center", + "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": "Small Text", + "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": [ + { + "link_doctype": "Sales Invoice", + "link_fieldname": "fee_schedule" + }, + { + "link_doctype": "Sales Order", + "link_fieldname": "fee_schedule" + } + ], + "modified": "2024-03-27 22:48:15.293321", + "modified_by": "Administrator", + "module": "Education", + "name": "Fee Schedule", + "naming_rule": "By \"Naming Series\" field", + "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 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [ + { + "color": "Orange", + "title": "Invoice Pending" + }, + { + "color": "Orange", + "title": "Order Pending" + }, + { + "color": "Orange", + "title": "In Process" + }, + { + "color": "Green", + "title": "Order Created" + }, + { + "color": "Green", + "title": "Invoice Created" + }, + { + "color": "Red", + "title": "Fee Creation Failed" + } + ] +} diff --git a/education/education/doctype/fee_schedule/fee_schedule.py b/education/education/doctype/fee_schedule/fee_schedule.py index 27c4d262..28dffde9 100644 --- a/education/education/doctype/fee_schedule/fee_schedule.py +++ b/education/education/doctype/fee_schedule/fee_schedule.py @@ -19,9 +19,25 @@ def onload(self): info = self.get_dashboard_info() self.set_onload("dashboard_info", info) + def before_submit(self): + self.status = self.get_status() + # def on_cancel(self): # frappe.db.set_value("Fee Schedule", self.name, "fee_creation_status", "Cancelled") + def get_status(self): + status = "" + if self.docstatus == 0: + status = "Draft" + elif self.docstatus == 1: + if frappe.db.get_single_value("Education Settings", "create_so"): + status = "Order Pending" + else: + status = "Invoice Pending" + elif self.docstatus == 2: + status = "Cancelled" + return status + def get_dashboard_info(self): info = { "total_paid": 0, @@ -31,7 +47,7 @@ def get_dashboard_info(self): fees_amount = frappe.db.sql( """select sum(grand_total), sum(outstanding_amount) from `tabSales Invoice` - where fee_schedule=%s and docstatus=1 and student is not null""", + where fee_schedule=%s and docstatus=1 and student is not null""", (self.name), ) @@ -49,7 +65,10 @@ def calculate_total_and_program(self): 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 + d.student_group, + self.academic_year, + self.academic_term, + self.student_category, ) no_of_students += cint(d.total_students) @@ -68,9 +87,12 @@ def calculate_total_and_program(self): @frappe.whitelist() def create_fees(self): - self.db_set("fee_creation_status", "In Process") + self.db_set("status", "In Process") + frappe.publish_realtime( - "fee_schedule_progress", {"progress": "0", "reload": 1}, user=frappe.session.user + "fee_schedule_progress", + {"progress": 0, "reload": 1}, + user=frappe.session.user, ) total_records = sum([int(d.total_students) for d in self.student_groups]) @@ -78,23 +100,25 @@ def create_fees(self): frappe.msgprint( _( """Fee records will be created in the background. - In case of any error the error message will be updated in the Schedule.""" + In case of any error the error message will be updated in the Schedule.""" ) ) enqueue( - generate_sales_invoice, + generate_fees, queue="default", timeout=6000, - event="generate_sales_invoice", + event="generate_fees", fee_schedule=self.name, ) else: - generate_sales_invoice(self.name) + generate_fees(self.name) -def generate_sales_invoice(fee_schedule): +def generate_fees(fee_schedule): + doc = frappe.get_doc("Fee Schedule", fee_schedule) error = False + create_so = frappe.db.get_single_value("Education Settings", "create_so") total_records = sum([int(d.total_students) for d in doc.student_groups]) created_records = 0 @@ -108,11 +132,14 @@ def generate_sales_invoice(fee_schedule): for student in students: try: student_id = student.student - create_sales_invoice(fee_schedule, student_id) + if create_so: + create_sales_order(fee_schedule, student_id) + else: + create_sales_invoice(fee_schedule, student_id) created_records += 1 frappe.publish_realtime( "fee_schedule_progress", - {"progress": str(int(created_records * 100 / total_records))}, + {"progress": int(created_records * 100 / total_records)}, user=frappe.session.user, ) @@ -124,52 +151,33 @@ def generate_sales_invoice(fee_schedule): 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, "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") + if create_so: + frappe.db.set_value("Fee Schedule", fee_schedule, "status", "Order Created") + else: + frappe.db.set_value("Fee Schedule", fee_schedule, "status", "Invoice Created") 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 + "fee_schedule_progress", + {"progress": 100, "reload": 1}, + user=frappe.session.user, ) -def create_sales_invoice(fee_schedule, student_id): - student = frappe.get_doc("Student", student_id) - if not student.customer: - student.set_missing_customer_details() - student.create_customer() - customer = frappe.db.get_value("Student", student.name, "customer") +def create_sales_invoice(fee_schedule, student_id, create_sales_order=False): + customer = get_customer_from_student(student_id) - sales_invoice_doc = get_mapped_doc( - "Fee Schedule", - fee_schedule, - { - "Fee Schedule": { - "doctype": "Sales Invoice", - "field_map": { - # change custom_fee_schedule to fee_schedule - "name": "fee_schedule", - "due_date": "due_date", - "posting_date": "posting_date", - }, - }, - "Fee Component": { - "doctype": "Sales Invoice Item", - "field_map": { - # Fee Component Field : Sales Invoice Item Field - "item": "item_code", - "amount": "price_list_rate", - "discount": "discount_percentage", - }, - }, - }, - ignore_permissions=True, + sales_invoice_doc = get_fees_mapped_doc( + fee_schedule=fee_schedule, + doctype="Sales Invoice", + student_id=student_id, + customer=customer, ) - sales_invoice_doc.student = student.name - sales_invoice_doc.customer = customer + if frappe.db.get_single_value( "Education Settings", "sales_invoice_posting_date_fee_schedule" ): @@ -185,6 +193,77 @@ def create_sales_invoice(fee_schedule, student_id): return sales_invoice_doc.name +def create_sales_order(fee_schedule, student_id): + customer = get_customer_from_student(student_id) + + sales_order_doc = get_fees_mapped_doc( + fee_schedule=fee_schedule, + doctype="Sales Order", + student_id=student_id, + customer=customer, + ) + + for item in sales_order_doc.items: + item.qty = 1 + + sales_order_doc.save() + + if frappe.db.get_single_value("Education Settings", "auto_submit_sales_order"): + sales_order_doc.submit() + + return sales_order_doc.name + + +def get_customer_from_student(student_id): + student = frappe.get_doc("Student", student_id) + if not student.customer: + student.set_missing_customer_details() + student.create_customer() + return frappe.db.get_value("Student", student.name, "customer") + + +def get_fees_mapped_doc(fee_schedule, doctype, student_id, customer): + table_map = { + "Fee Schedule": { + "doctype": doctype, + "field_map": { + # Fee Schedule Field : doctype Field + "name": "fee_schedule", + }, + }, + "Fee Component": { + "doctype": "Sales Invoice Item" + if doctype == "Sales Invoice" + else "Sales Order Item", + "field_map": { + # Fee Component Field : Child doctype Field + "item": "item_code", + "amount": "price_list_rate", + "discount": "discount_percentage", + }, + }, + } + if doctype == "Sales Invoice": + table_map["Fee Schedule"]["field_map"]["due_date"] = "due_date" + table_map["Fee Schedule"]["field_map"]["posting_date"] = "posting_date" + else: + table_map["Fee Schedule"]["field_map"]["due_date"] = "delivery_date" + if frappe.db.get_single_value( + "Education Settings", "sales_order_transaction_date_fee_schedule" + ): + table_map["Fee Schedule"]["field_map"]["posting_date"] = "transaction_date" + + doc = get_mapped_doc( + "Fee Schedule", + fee_schedule, + table_map, + ignore_permissions=True, + ) + doc.student = student_id + doc.customer = customer + return doc + + # gives program name for multiple enrollments in a calendar year def get_students( student_group, academic_year, academic_term=None, student_category=None @@ -196,13 +275,13 @@ def get_students( 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, pe.name as enrollment - 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( + select pe.student, pe.student_name, pe.program, pe.student_batch_name, pe.name as enrollment + 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), diff --git a/education/education/doctype/fee_schedule/fee_schedule_list.js b/education/education/doctype/fee_schedule/fee_schedule_list.js index e70f0f7f..325c9d4c 100644 --- a/education/education/doctype/fee_schedule/fee_schedule_list.js +++ b/education/education/doctype/fee_schedule/fee_schedule_list.js @@ -1,14 +1,3 @@ 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"), "green", "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"), "orange", "fee_creation_status,=,"]; - } - } + add_fields: ["status", "due_date", "grand_total"], }; diff --git a/education/education/doctype/fee_schedule/test_fee_schedule.py b/education/education/doctype/fee_schedule/test_fee_schedule.py index c291aed3..3ef226ae 100644 --- a/education/education/doctype/fee_schedule/test_fee_schedule.py +++ b/education/education/doctype/fee_schedule/test_fee_schedule.py @@ -1,8 +1,91 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +import frappe +from frappe.tests.utils import FrappeTestCase +from education.education.doctype.fee_schedule.fee_schedule import generate_fees +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_fee_category, + create_program, + create_fee_structure, + create_student, + create_program_enrollment, + create_student_group, + create_fee_schedule, +) -class TestFeeSchedule(unittest.TestCase): - pass + +class TestFeeSchedule(FrappeTestCase): + def setUp(self): + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" + ) + create_academic_term( + term_name="Term 2", term_start_date="2023-10-01", term_end_date="2024-03-31" + ) + create_program("Class 1") + create_fee_category("Tuition Fee") + create_fee_category("Library Fee") + fee_components = [ + {"fees_category": "Tuition Fee", "amount": 2000, "discount": 0}, + {"fees_category": "Library Fee", "amount": 2000, "discount": 0}, + ] + fee_structure = create_fee_structure(components=fee_components, submit=1) + + student = create_student() + create_program_enrollment(student_name=student.name, submit=1) + + create_student_group() + + def tearDown(self): + frappe.db.rollback() + + def test_fee_schedule(self): + fee_schedule = create_fee_schedule(submit=1) + total_students = 0 + for group in fee_schedule.student_groups: + total_students += group.total_students + + self.assertEqual(fee_schedule.docstatus, 1) + self.assertEqual(total_students, 1) + self.assertEqual(fee_schedule.grand_total, total_students * fee_schedule.total_amount) + + def test_sales_invoice_creation_flow(self): + fee_schedule = create_fee_schedule(submit=1, due_date="2024-05-01") + # sales_invoice_posting_date_fee_schedule set it as 1 + self.assertEqual(fee_schedule.status, "Invoice Pending") + self.assertNotEqual(fee_schedule.status, "Order Pending") + generate_fees(fee_schedule.name) + sales_invoices = frappe.get_all( + "Sales Invoice", filters={"fee_schedule": fee_schedule.name} + ) + sales_order = frappe.get_all( + "Sales Order", filters={"fee_schedule": fee_schedule.name} + ) + self.assertEqual(len(sales_invoices), 1) + self.assertEqual(len(sales_order), 0) + fee_schedule_status = frappe.db.get_value("Fee Schedule", fee_schedule.name, "status") + self.assertEqual(fee_schedule_status, "Invoice Created") + + def test_sales_order_creation_flow(self): + # create_so from education settings set to 1 + frappe.db.set_value("Education Settings", "Education Settings", "create_so", 1) + fee_schedule = create_fee_schedule(submit=1, due_date="2024-05-01") + + self.assertEqual(fee_schedule.status, "Order Pending") + self.assertNotEqual(fee_schedule.status, "Invoice Pending") + generate_fees(fee_schedule.name) + sales_order = frappe.get_all( + "Sales Order", filters={"fee_schedule": fee_schedule.name} + ) + sales_invoices = frappe.get_all( + "Sales Invoice", filters={"fee_schedule": fee_schedule.name} + ) + self.assertEqual(len(sales_order), 1) + self.assertEqual(len(sales_invoices), 0) + fee_schedule_status = frappe.db.get_value("Fee Schedule", fee_schedule.name, "status") + self.assertEqual(fee_schedule_status, "Order Created") diff --git a/education/education/doctype/fee_structure/fee_structure.py b/education/education/doctype/fee_structure/fee_structure.py index caa5c1f3..92ae3a6e 100644 --- a/education/education/doctype/fee_structure/fee_structure.py +++ b/education/education/doctype/fee_structure/fee_structure.py @@ -21,6 +21,7 @@ def calculate_total(self): """Calculates total amount.""" self.total_amount = 0 for d in self.components: + d.total = flt(d.amount) - (d.amount * (flt(d.discount) / 100)) self.total_amount += d.total def validate_discount(self): @@ -188,6 +189,7 @@ def validate_due_date(due_date, idx): @frappe.whitelist() def make_term_wise_fee_schedule(source_name, target_doc=None): + return get_mapped_doc( "Fee Structure", source_name, diff --git a/education/education/doctype/fee_structure/test_fee_structure.py b/education/education/doctype/fee_structure/test_fee_structure.py index ebe0fea5..717d009b 100644 --- a/education/education/doctype/fee_structure/test_fee_structure.py +++ b/education/education/doctype/fee_structure/test_fee_structure.py @@ -1,10 +1,50 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest +import frappe +from frappe.tests.utils import FrappeTestCase +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_fee_category, + create_program, + create_fee_structure, +) -# test_records = frappe.get_test_records('Fee Structure') +class TestFeeStructure(FrappeTestCase): + def setUp(self): + # add functions which are needed for setting up the test + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" + ) + create_academic_term( + term_name="Term 2", term_start_date="2023-10-01", term_end_date="2024-03-31" + ) + create_fee_category("Tuition Fee") + create_fee_category("Library Fee") + create_program("Class 1") -class TestFeeStructure(unittest.TestCase): - pass + def tearDown(self): + frappe.db.rollback() + + def test_fee_structure_creation(self): + fee_components = [ + {"fees_category": "Tuition Fee", "amount": 2000, "discount": 0}, + {"fees_category": "Library Fee", "amount": 2000, "discount": 0}, + ] + fee_structure = create_fee_structure(components=fee_components, submit=1) + + self.assertEqual(fee_structure.docstatus, 1) + self.assertEqual(fee_structure.total_amount, 4000) + + def test_fee_structure_with_discounts(self): + fee_components = [ + {"fees_category": "Tuition Fee", "amount": 2000, "discount": 20}, + {"fees_category": "Library Fee", "amount": 2000, "discount": 50}, + ] + fee_structure = create_fee_structure(components=fee_components, submit=1) + + self.assertEqual(fee_structure.docstatus, 1) + self.assertEqual(fee_structure.total_amount, 2600) diff --git a/education/education/doctype/fee_structure/test_records.json b/education/education/doctype/fee_structure/test_records.json deleted file mode 100644 index cdd00be3..00000000 --- a/education/education/doctype/fee_structure/test_records.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "doctype": "Fee Structure", - "academic_year": "2017-2018", - "academic_term": "2017-2018 (_Test AT1)", - "components": [ - { - "fees_category": "Tuition Fee", - "amount": 40000 - }, - { - "fees_category": "Transportation Fee", - "amount": 10000 - } - ], - "total_amount": 50000, - "receivable_account": "_Test Receivable - _TC", - "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC", - "company": "_Test Company" - }, - { - "doctype": "Fee Structure", - "academic_year": "2017-2018", - "academic_term": "2017-2018 (_Test AT2)", - "components": [ - { - "fees_category": "Tuition Fee", - "amount": 40000 - }, - { - "fees_category": "Transportation Fee", - "amount": 10000 - } - ], - "total_amount": 50000, - "receivable_account": "_Test Receivable - _TC", - "income_account": "Sales - _TC", - "cost_center": "_Test Cost Center - _TC", - "company": "_Test Company" - } -] \ No newline at end of file diff --git a/education/education/doctype/fees/test_fees.py b/education/education/doctype/fees/test_fees.py index 4e84d844..4dbd5b7f 100644 --- a/education/education/doctype/fees/test_fees.py +++ b/education/education/doctype/fees/test_fees.py @@ -1,60 +1,17 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest import frappe from frappe.utils import nowdate from frappe.utils.make_random import get_random +from frappe.tests.utils import FrappeTestCase -from education.education.doctype.program.test_program import \ - make_program_and_linked_courses +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) +class TestFees(FrappeTestCase): + # deprecated + pass diff --git a/education/education/doctype/grading_scale/test_grading_scale.py b/education/education/doctype/grading_scale/test_grading_scale.py index 09a092cc..4c66e385 100644 --- a/education/education/doctype/grading_scale/test_grading_scale.py +++ b/education/education/doctype/grading_scale/test_grading_scale.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Grading Scale') -class TestGradingScale(unittest.TestCase): +class TestGradingScale(FrappeTestCase): pass diff --git a/education/education/doctype/guardian/test_guardian.py b/education/education/doctype/guardian/test_guardian.py index de3638c3..c74d1e59 100644 --- a/education/education/doctype/guardian/test_guardian.py +++ b/education/education/doctype/guardian/test_guardian.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Guardian') -class TestGuardian(unittest.TestCase): +class TestGuardian(FrappeTestCase): pass diff --git a/education/education/doctype/instructor/test_instructor.py b/education/education/doctype/instructor/test_instructor.py index ee1e81dd..4c694ee8 100644 --- a/education/education/doctype/instructor/test_instructor.py +++ b/education/education/doctype/instructor/test_instructor.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Instructor') -class TestInstructor(unittest.TestCase): +class TestInstructor(FrappeTestCase): pass diff --git a/education/education/doctype/program/test_program.py b/education/education/doctype/program/test_program.py index 36bc7c30..141c6e1c 100644 --- a/education/education/doctype/program/test_program.py +++ b/education/education/doctype/program/test_program.py @@ -1,14 +1,15 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # 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 + make_course, + make_course_and_linked_topic, +) +from education.education.doctype.topic.test_topic import make_topic_and_linked_content +from frappe.tests.utils import FrappeTestCase test_data = { "program_name": "_Test Program", @@ -37,7 +38,7 @@ } -class TestProgram(unittest.TestCase): +class TestProgram(FrappeTestCase): def setUp(self): make_program_and_linked_courses( "_Test Program 1", ["_Test Course 1", "_Test Course 2"] diff --git a/education/education/doctype/program/test_records.json b/education/education/doctype/program/test_records.json deleted file mode 100644 index 40136958..00000000 --- a/education/education/doctype/program/test_records.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "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_enrollment/program_enrollment.py b/education/education/doctype/program_enrollment/program_enrollment.py index 61590877..62071f55 100644 --- a/education/education/doctype/program_enrollment/program_enrollment.py +++ b/education/education/doctype/program_enrollment/program_enrollment.py @@ -8,7 +8,10 @@ from frappe.model.document import Document from frappe.query_builder.functions import Min from frappe.utils import comma_and, get_link_to_form, getdate -from education.education.doctype.fee_schedule.fee_schedule import create_sales_invoice +from education.education.doctype.fee_schedule.fee_schedule import ( + create_sales_invoice, + create_sales_order, +) class ProgramEnrollment(Document): @@ -102,16 +105,26 @@ def update_student_joining_date(self): def make_fee_records(self): from education.education.api import get_fee_components - sales_invoice_list = [] + create_so = frappe.db.get_single_value("Education Settings", "create_so") + + fees_list = [] + doctype = "" for d in self.fees: - sales_invoice = create_sales_invoice(d.fee_schedule, self.student) - sales_invoice_list.append(sales_invoice) - if sales_invoice_list: - sales_invoice_list = [ - """%s""" % (fee, fee) - for fee in sales_invoice_list + if create_so: + sales_order = create_sales_order(d.fee_schedule, self.student) + doctype = "Sales Order" + fees_list.append(sales_order) + else: + sales_invoice = create_sales_invoice(d.fee_schedule, self.student) + doctype = "Sales Invoice" + fees_list.append(sales_invoice) + + if fees_list: + fees_list = [ + """%s""" % (doctype, fee, fee) + for fee in fees_list ] - msgprint(_("Fee Records Created - {0}").format(comma_and(sales_invoice_list))) + msgprint(_("Fee Records Created - {0}").format(comma_and(fees_list))) @frappe.whitelist() def get_courses(self): @@ -143,23 +156,6 @@ def get_all_course_enrollments(self): 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 - def delete_course_enrollments(self): for course_enrollment in self.get_all_course_enrollments(): frappe.delete_doc("Course Enrollment", course_enrollment.name) @@ -175,12 +171,12 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): doctype = "Program Course" 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( + 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 ), { @@ -213,14 +209,14 @@ def get_students(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql( """select - name, student_name from tabStudent - where - name not in (%s) - and - `%s` LIKE %s - order by - idx desc, name - limit %s, %s""" + name, student_name 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/test_program_enrollment.py b/education/education/doctype/program_enrollment/test_program_enrollment.py index 986f9046..3a5dda6a 100644 --- a/education/education/doctype/program_enrollment/test_program_enrollment.py +++ b/education/education/doctype/program_enrollment/test_program_enrollment.py @@ -1,42 +1,31 @@ # Copyright (c) 2015, Frappe and Contributors # See license.txt -import unittest import frappe +from frappe.tests.utils import FrappeTestCase -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) +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_program, + create_student, + create_program_enrollment, + create_student_group, +) -class TestProgramEnrollment(unittest.TestCase): +class TestProgramEnrollment(FrappeTestCase): 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"] + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" ) + create_program("Class 1") 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() + student = create_student() + create_program_enrollment(student_name=student.name, submit=1) 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() + frappe.db.rollback() 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 index e806792e..d9cf0db3 100644 --- a/education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py +++ b/education/education/doctype/program_enrollment_tool/test_program_enrollment_tool.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestProgramEnrollmentTool(unittest.TestCase): + +class TestProgramEnrollmentTool(FrappeTestCase): pass diff --git a/education/education/doctype/question/test_question.py b/education/education/doctype/question/test_question.py index 7506d84a..419a80de 100644 --- a/education/education/doctype/question/test_question.py +++ b/education/education/doctype/question/test_question.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestQuestion(unittest.TestCase): + +class TestQuestion(FrappeTestCase): pass diff --git a/education/education/doctype/quiz/test_quiz.py b/education/education/doctype/quiz/test_quiz.py index a69a0c1d..1b759464 100644 --- a/education/education/doctype/quiz/test_quiz.py +++ b/education/education/doctype/quiz/test_quiz.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestQuiz(unittest.TestCase): + +class TestQuiz(FrappeTestCase): pass diff --git a/education/education/doctype/quiz_activity/test_quiz_activity.py b/education/education/doctype/quiz_activity/test_quiz_activity.py index 1040c1a8..8362b3f0 100644 --- a/education/education/doctype/quiz_activity/test_quiz_activity.py +++ b/education/education/doctype/quiz_activity/test_quiz_activity.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestQuizActivity(unittest.TestCase): + +class TestQuizActivity(FrappeTestCase): pass diff --git a/education/education/doctype/quiz_result/test_quiz_result.py b/education/education/doctype/quiz_result/test_quiz_result.py index 12098a77..bc8d2e09 100644 --- a/education/education/doctype/quiz_result/test_quiz_result.py +++ b/education/education/doctype/quiz_result/test_quiz_result.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestQuizResult(unittest.TestCase): + +class TestQuizResult(FrappeTestCase): pass diff --git a/education/education/doctype/room/test_room.py b/education/education/doctype/room/test_room.py index ea0baf21..35c74bc9 100644 --- a/education/education/doctype/room/test_room.py +++ b/education/education/doctype/room/test_room.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Room') -class TestRoom(unittest.TestCase): +class TestRoom(FrappeTestCase): pass diff --git a/education/education/doctype/school_house/test_school_house.py b/education/education/doctype/school_house/test_school_house.py index 7fe12d74..27d3d71d 100644 --- a/education/education/doctype/school_house/test_school_house.py +++ b/education/education/doctype/school_house/test_school_house.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestSchoolHouse(unittest.TestCase): + +class TestSchoolHouse(FrappeTestCase): pass diff --git a/education/education/doctype/student/test_student.py b/education/education/doctype/student/test_student.py index 25009c1e..e9f4494b 100644 --- a/education/education/doctype/student/test_student.py +++ b/education/education/doctype/student/test_student.py @@ -1,85 +1,24 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest import frappe -from education.education.doctype.program.test_program import \ - make_program_and_linked_courses +from education.education.doctype.program.test_program import ( + make_program_and_linked_courses, +) test_records = frappe.get_test_records("Student") +from frappe.tests.utils import FrappeTestCase +from education.education.test_utils import create_student -class TestStudent(unittest.TestCase): +class TestStudent(FrappeTestCase): 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"] - ) + student = create_student() 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() + self.assertTrue(bool(frappe.db.exists("User", "test@example.com"))) 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 + frappe.db.rollback() diff --git a/education/education/doctype/student_admission/test_student_admission.py b/education/education/doctype/student_admission/test_student_admission.py index 82c720a3..c10d80bd 100644 --- a/education/education/doctype/student_admission/test_student_admission.py +++ b/education/education/doctype/student_admission/test_student_admission.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Admission') -class TestStudentAdmission(unittest.TestCase): +class TestStudentAdmission(FrappeTestCase): pass diff --git a/education/education/doctype/student_applicant/test_student_applicant.py b/education/education/doctype/student_applicant/test_student_applicant.py index bd0e360d..af59a836 100644 --- a/education/education/doctype/student_applicant/test_student_applicant.py +++ b/education/education/doctype/student_applicant/test_student_applicant.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Applicant') -class TestStudentApplicant(unittest.TestCase): +class TestStudentApplicant(FrappeTestCase): pass diff --git a/education/education/doctype/student_attendance/test_student_attendance.py b/education/education/doctype/student_attendance/test_student_attendance.py index e5b1841e..81d0687d 100644 --- a/education/education/doctype/student_attendance/test_student_attendance.py +++ b/education/education/doctype/student_attendance/test_student_attendance.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Attendance') -class TestStudentAttendance(unittest.TestCase): +class TestStudentAttendance(FrappeTestCase): pass 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 index c15036fe..e3361ca5 100644 --- a/education/education/doctype/student_attendance_tool/test_student_attendance_tool.py +++ b/education/education/doctype/student_attendance_tool/test_student_attendance_tool.py @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestStudentAttendanceTool(unittest.TestCase): + +class TestStudentAttendanceTool(FrappeTestCase): pass 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 index bf9639bf..18d58c01 100644 --- a/education/education/doctype/student_batch_name/test_student_batch_name.py +++ b/education/education/doctype/student_batch_name/test_student_batch_name.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Batch Name') -class TestStudentBatchName(unittest.TestCase): +class TestStudentBatchName(FrappeTestCase): pass diff --git a/education/education/doctype/student_category/test_student_category.py b/education/education/doctype/student_category/test_student_category.py index 5671e9fa..5cd4f492 100644 --- a/education/education/doctype/student_category/test_student_category.py +++ b/education/education/doctype/student_category/test_student_category.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Category') -class TestStudentCategory(unittest.TestCase): +class TestStudentCategory(FrappeTestCase): pass diff --git a/education/education/doctype/student_group/student_group.js b/education/education/doctype/student_group/student_group.js index 41a67c83..66739abf 100644 --- a/education/education/doctype/student_group/student_group.js +++ b/education/education/doctype/student_group/student_group.js @@ -1,6 +1,6 @@ frappe.ui.form.on('Student Group', { - onload: function(frm) { - frm.set_query('academic_term', function() { + onload: function (frm) { + frm.set_query('academic_term', function () { return { filters: { 'academic_year': (frm.doc.academic_year) @@ -8,8 +8,8 @@ frappe.ui.form.on('Student Group', { }; }); if (!frm.__islocal) { - frm.set_query('student', 'students', function() { - return{ + frm.set_query('student', 'students', function () { + return { query: 'education.education.doctype.student_group.student_group.fetch_students', filters: { 'academic_year': frm.doc.academic_year, @@ -26,10 +26,10 @@ frappe.ui.form.on('Student Group', { } }, - refresh: function(frm) { + refresh: function (frm) { if (!frm.doc.__islocal) { - frm.add_custom_button(__('Add Guardians to Email Group'), function() { + frm.add_custom_button(__('Add Guardians to Email Group'), function () { frappe.call({ method: 'education.education.api.update_email_group', args: { @@ -39,7 +39,7 @@ frappe.ui.form.on('Student Group', { }); }, __('Actions')); - frm.add_custom_button(__('Student Attendance Tool'), function() { + frm.add_custom_button(__('Student Attendance Tool'), function () { frappe.route_options = { based_on: 'Student Group', student_group: frm.doc.name @@ -47,14 +47,14 @@ frappe.ui.form.on('Student Group', { frappe.set_route('Form', 'Student Attendance Tool', 'Student Attendance Tool'); }, __('Tools')); - frm.add_custom_button(__('Course Scheduling Tool'), function() { + 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() { + frm.add_custom_button(__('Newsletter'), function () { frappe.route_options = { 'Newsletter Email Group.email_group': frm.doc.name } @@ -64,7 +64,7 @@ frappe.ui.form.on('Student Group', { } }, - group_based_on: function(frm) { + group_based_on: function (frm) { if (frm.doc.group_based_on == 'Batch') { frm.doc.course = null; frm.set_df_property('program', 'reqd', 1); @@ -80,13 +80,13 @@ frappe.ui.form.on('Student Group', { } }, - get_students: function(frm) { + 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) { + $.each(frm.doc.students, function (_i, d) { student_list.push(d.student); - if (d.group_roll_number>max_roll_no) { + if (d.group_roll_number > max_roll_no) { max_roll_no = d.group_roll_number; } }); @@ -99,14 +99,15 @@ frappe.ui.form.on('Student Group', { '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, + 'batch': frm.doc.batch, + 'student_category': frm.doc.student_category, 'course': frm.doc.course }, - callback: function(r) { + callback: function (r) { if (r.message) { - $.each(r.message, function(i, d) { - if(!in_list(student_list, d.student)) { + console.log(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; @@ -131,10 +132,10 @@ frappe.ui.form.on('Student Group', { }); frappe.ui.form.on('Student Group Instructor', { - instructors_add: function(frm){ - frm.fields_dict['instructors'].grid.get_field('instructor').get_query = function(doc){ + 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){ + $.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/test_records.json b/education/education/doctype/student_group/test_records.json deleted file mode 100644 index 4c4e042b..00000000 --- a/education/education/doctype/student_group/test_records.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "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 index 3e955a06..ad408fc8 100644 --- a/education/education/doctype/student_group/test_student_group.py +++ b/education/education/doctype/student_group/test_student_group.py @@ -1,52 +1,38 @@ # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -import unittest import frappe import education.education +from frappe.tests.utils import FrappeTestCase +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_program, + create_student, + create_program_enrollment, + create_student_group, +) + + +class TestStudentGroup(FrappeTestCase): + def setUp(self): + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" + ) + create_program("Class 1") + student = create_student() + create_program_enrollment(student_name=student.name, submit=1) + create_student_group() + def tearDown(self): + frappe.db.rollback() -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 + student_group = frappe.get_doc("Student Group", "Test Student Group") + self.assertEqual( + max([d.group_roll_number for d in student_group.students]), + len(student_group.students), ) 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 index 8722f973..d9597149 100644 --- 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 @@ -1,8 +1,9 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestStudentGroupCreationTool(unittest.TestCase): + +class TestStudentGroupCreationTool(FrappeTestCase): pass diff --git a/education/education/doctype/student_language/test_student_language.py b/education/education/doctype/student_language/test_student_language.py index d1a8b6d5..7d15e1a5 100644 --- a/education/education/doctype/student_language/test_student_language.py +++ b/education/education/doctype/student_language/test_student_language.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Language') -class TestStudentLanguage(unittest.TestCase): +class TestStudentLanguage(FrappeTestCase): pass 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 index 9122fbc0..b8971c9a 100644 --- a/education/education/doctype/student_leave_application/test_student_leave_application.py +++ b/education/education/doctype/student_leave_application/test_student_leave_application.py @@ -1,21 +1,41 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest import frappe +from frappe.tests.utils import FrappeTestCase 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 +from education.education.test_utils import ( + create_academic_year, + create_academic_term, + create_program, + create_student, + create_program_enrollment, + create_student_group, + before_tests, +) -class TestStudentLeaveApplication(unittest.TestCase): +class TestStudentLeaveApplication(FrappeTestCase): def setUp(self): + frappe.db.set_value( + "Company", "Wind Power LLC", "default_holiday_list", "Test Holiday List" + ) frappe.db.sql("""delete from `tabStudent Leave Application`""") create_holiday_list() + create_academic_year() + create_academic_term( + term_name="Term 1", term_start_date="2023-04-01", term_end_date="2023-09-30" + ) + create_program() + student = create_student() + create_program_enrollment(student_name=student.name, submit=1) + create_student_group() + + def tearDown(self): + frappe.db.rollback() def test_attendance_record_creation(self): leave_application = create_leave_application() @@ -77,10 +97,6 @@ def test_holiday(self): ) ) - 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() @@ -88,7 +104,7 @@ def create_leave_application(from_date=None, to_date=None, mark_as_present=0, su 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.student_group = "Test Student Group" 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 @@ -108,16 +124,15 @@ def create_student_attendance(date=None, status=None): "student": student.name, "status": status if status else "Present", "date": date if date else getdate(), - "student_group": get_random_group().name, + "student_group": "Test Student Group", } ).insert() return attendance def get_student(): - return create_student( - dict(email="test_student@gmail.com", first_name="Test", last_name="Student") - ) + student = frappe.get_doc("Student", {"student_email_id": "test@example.com"}) + return student def create_holiday_list(): diff --git a/education/education/doctype/student_log/test_student_log.py b/education/education/doctype/student_log/test_student_log.py index fef1ab56..1f1f3648 100644 --- a/education/education/doctype/student_log/test_student_log.py +++ b/education/education/doctype/student_log/test_student_log.py @@ -1,10 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest + +from frappe.tests.utils import FrappeTestCase # test_records = frappe.get_test_records('Student Log') -class TestStudentLog(unittest.TestCase): +class TestStudentLog(FrappeTestCase): pass 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 index e37881f0..96de8ae9 100644 --- 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 @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestStudentReportGenerationTool(unittest.TestCase): + +class TestStudentReportGenerationTool(FrappeTestCase): pass diff --git a/education/education/doctype/topic/test_topic.py b/education/education/doctype/topic/test_topic.py index ce24dee4..c42ef00d 100644 --- a/education/education/doctype/topic/test_topic.py +++ b/education/education/doctype/topic/test_topic.py @@ -1,12 +1,12 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest import frappe +from frappe.tests.utils import FrappeTestCase -class TestTopic(unittest.TestCase): +class TestTopic(FrappeTestCase): def setUp(self): make_topic_and_linked_content( "_Test Topic 1", [{"type": "Article", "name": "_Test Article 1"}] diff --git a/education/education/doctype/topic_content/test_topic_content.py b/education/education/doctype/topic_content/test_topic_content.py index 56bb4096..8f4c6f18 100644 --- a/education/education/doctype/topic_content/test_topic_content.py +++ b/education/education/doctype/topic_content/test_topic_content.py @@ -1,8 +1,9 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import unittest +from frappe.tests.utils import FrappeTestCase -class TestTopicContent(unittest.TestCase): + +class TestTopicContent(FrappeTestCase): pass diff --git a/education/education/test_utils.py b/education/education/test_utils.py new file mode 100644 index 00000000..59fbe472 --- /dev/null +++ b/education/education/test_utils.py @@ -0,0 +1,258 @@ +import frappe +from education.education.doctype.fee_schedule.fee_schedule import get_fee_structure +from education.education.doctype.student_group.student_group import get_students +from erpnext.setup.utils import enable_all_roles_and_domains +from frappe.utils import now_datetime, add_years, nowdate + + +DEFAULT_PROGRAM_NAME = "Class 1" +DEFAULT_ACADEMIC_YEAR = "2023-2024" +DEFAULT_ACADEMIC_TERM = "2023-2024 (Term 1)" +DEFAULT_STUDENT_GROUP = "Test Student Group" +DEFAULT_GROUP_BASED_ON = "Batch" +DEFAULT_FEES_CATEGORY = "Tuition Fee" +DEFAULT_STUDENT_EMAIL_ID = "test@example.com" + + +def before_tests(): + frappe.clear_cache() + from frappe.desk.page.setup_wizard.setup_wizard import setup_complete + + year = now_datetime().year + if not frappe.get_list("Company"): + setup_complete( + { + "currency": "INR", + "full_name": "Test User", + "company_name": "_Test Company", + "timezone": "Asia/Kolkata", + "company_abbr": "_TC", + "industry": "Manufacturing", + "country": "India", + "fy_start_date": f"{year}-01-01", + "fy_end_date": f"{year}-12-31", + "language": "english", + "company_tagline": "Testing", + "email": "test@erpnext.com", + "password": "test", + "chart_of_accounts": "Standard", + } + ) + + frappe.db.set_value( + "Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0 + ) + enable_all_roles_and_domains() + make_holiday_list() + frappe.db.commit() + + +def make_holiday_list(holiday_list_name="Test Holiday List"): + if not frappe.db.get_value("Holiday List", holiday_list_name): + holiday_list = frappe.get_doc( + { + "doctype": "Holiday List", + "holiday_list_name": holiday_list_name, + "from_date": nowdate(), + "to_date": add_years(nowdate(), 1), + "weekly_off": "Sunday", + } + ).insert() + holiday_list.get_weekly_off_dates() + holiday_list.save() + + +def create_academic_year( + academic_year_name=DEFAULT_ACADEMIC_YEAR, year_start_date=None, year_end_date=None +): + if frappe.db.exists("Academic Year", {"academic_year_name": DEFAULT_ACADEMIC_YEAR}): + return + + academic_year = frappe.new_doc("Academic Year") + academic_year.academic_year_name = academic_year_name + academic_year.year_start_date = year_start_date or "2023-04-01" + academic_year.year_end_date = year_end_date or "2024-03-31" + academic_year.save() + + +def create_academic_term( + term_start_date, term_end_date, academic_year=DEFAULT_ACADEMIC_YEAR, term_name="Term 1" +): + if frappe.db.exists("Academic Term", {"term_name": "Term 1"}): + return + + academic_term = frappe.new_doc("Academic Term") + academic_term.academic_year = academic_year + academic_term.term_name = term_name + academic_term.term_start_date = term_start_date + academic_term.term_end_date = term_end_date + academic_term.save() + + +def create_fee_category(category_name=DEFAULT_FEES_CATEGORY): + if frappe.db.exists("Fee Category", {"category_name": category_name}): + return frappe.get_doc("Fee Category", {"category_name": category_name}) + fee_category = frappe.new_doc("Fee Category") + fee_category.category_name = category_name + fee_category.save() + + +def create_program(name=DEFAULT_PROGRAM_NAME): + if frappe.db.exists("Program", {"program_name": name}): + return + program = frappe.new_doc("Program") + program.program_name = name + program.save() + + +def create_fee_structure( + academic_year=DEFAULT_ACADEMIC_YEAR, + academic_term=DEFAULT_ACADEMIC_TERM, + program=DEFAULT_PROGRAM_NAME, + components=None, + submit=False, +): + fee_structure = frappe.new_doc("Fee Structure") + fee_structure.academic_year = academic_year or DEFAULT_ACADEMIC_YEAR + fee_structure.academic_term = academic_term or DEFAULT_ACADEMIC_TERM + fee_structure.program = program or DEFAULT_PROGRAM_NAME + for i in components: + fee_structure.append( + "components", + { + "fees_category": i.get("fees_category"), + "amount": i.get("amount"), + "discount": i.get("discount"), + "total": i.get("total"), + }, + ) + fee_structure.save() + if submit: + fee_structure.submit() + return fee_structure + + +def create_student( + first_name="Test", last_name="Student", student_email_id=DEFAULT_STUDENT_EMAIL_ID +): + + if frappe.db.exists("Student", {"student_email_id": student_email_id}): + return frappe.get_doc("Student", {"student_email_id": student_email_id}) + + student = frappe.new_doc("Student") + student.first_name = first_name + student.last_name = last_name + student.student_email_id = student_email_id + student.save() + return student + + +def create_program_enrollment( + student_name, + program=DEFAULT_PROGRAM_NAME, + academic_year=DEFAULT_ACADEMIC_YEAR, + academic_term=DEFAULT_ACADEMIC_TERM, + enrollment_date="2023-04-01", + submit=False, +): + program_enrollment = frappe.new_doc("Program Enrollment") + program_enrollment.student = student_name + program_enrollment.program = program + program_enrollment.academic_year = academic_year + program_enrollment.academic_term = academic_term + program_enrollment.enrollment_date = enrollment_date + program_enrollment.save() + if submit: + program_enrollment.submit() + return program_enrollment + + +def create_student_group( + student_group_name=DEFAULT_STUDENT_GROUP, + academic_year=DEFAULT_ACADEMIC_YEAR, + academic_term=DEFAULT_ACADEMIC_TERM, + group_based_on=DEFAULT_GROUP_BASED_ON, + program=DEFAULT_PROGRAM_NAME, +): + if frappe.db.exists("Student Group", {"student_group_name": student_group_name}): + return frappe.get_doc("Student Group", {"student_group_name": student_group_name}) + student_group = frappe.new_doc("Student Group") + student_group.student_group_name = student_group_name + student_group.academic_year = academic_year + student_group.academic_term = academic_term + student_group.group_based_on = group_based_on + student_group.program = program + + students_in_group = get_students(academic_year, group_based_on, academic_term, program) + + for student in students_in_group: + student_group.append("students", {"student": student.get("student")}) + student_group.save() + + return student_group + + +def create_fee_schedule( + academic_year=DEFAULT_ACADEMIC_YEAR, due_date="2023-04-01", submit=False +): + fee_structure = frappe.db.get_value( + "Fee Structure", {"academic_year": academic_year}, "name" + ) + + fee_schedule = frappe.new_doc("Fee Schedule") + fee_schedule.fee_structure = fee_structure + fee_schedule = get_fee_structure(fee_schedule) + fee_schedule.due_date = due_date + + student_groups = frappe.db.get_list( + "Student Group", + { + "academic_year": DEFAULT_ACADEMIC_YEAR, + "academic_term": DEFAULT_ACADEMIC_TERM, + "student_group_name": DEFAULT_STUDENT_GROUP, + }, + "name", + ) + for group in student_groups: + fee_schedule.append("student_groups", {"student_group": group.get("name")}) + + fee_schedule.save() + if submit: + fee_schedule.submit() + + return fee_schedule + + +def create_instructor(instructor_name="Test Instructor"): + if not frappe.db.exists("Instructor", {"instructor_name": instructor_name}): + instructor = frappe.new_doc("Instructor") + instructor.instructor_name = instructor_name + instructor.save() + + +def create_course(course_name="Test Course"): + if not frappe.db.exists("Course", {"course_name": course_name}): + course = frappe.new_doc("Course") + course.course_name = course_name + course.save() + + +def create_room(room_name="Test Room"): + if not frappe.db.exists("Room", {"room_name": room_name}): + room = frappe.new_doc("Room") + room.room_name = room_name + room.save() + + +def create_grading_scale(grading_scale_name="_Test Grading Scale"): + if frappe.db.exists("Grading Scale", grading_scale_name): + return + + grading_scale = frappe.new_doc("Grading Scale") + grading_scale.grading_scale_name = grading_scale_name + grades = {"A": 80, "B": 70, "C": 60, "D": 50, "F": 0} + for grade, threshold in grades.items(): + grading_scale.append("intervals", {"grade_code": grade, "threshold": threshold}) + + grading_scale.save() + grading_scale.submit() diff --git a/education/hooks.py b/education/hooks.py index 4094e7b3..52479933 100644 --- a/education/hooks.py +++ b/education/hooks.py @@ -222,7 +222,7 @@ # Testing # ------- -# before_tests = "education.install.before_tests" +before_tests = "education.education.test_utils.before_tests" # Overriding Methods # ------------------------------ diff --git a/education/install.py b/education/install.py index 40985062..fc57d464 100644 --- a/education/install.py +++ b/education/install.py @@ -69,4 +69,32 @@ def get_custom_fields(): "insert_after": "column_break_ejcc", }, ], + "Sales Order": [ + { + "fieldname": "student_info_section", + "fieldtype": "Section Break", + "label": "Student Info", + "collapsible": 1, + "insert_after": "ignore_pricing_rule", + }, + { + "fieldname": "student", + "fieldtype": "Link", + "label": "Student", + "options": "Student", + "insert_after": "student_info_section", + }, + { + "fieldname": "column_break_ejcc", + "fieldtype": "Column Break", + "insert_after": "student", + }, + { + "fieldname": "fee_schedule", + "fieldtype": "Link", + "label": "Fee Schedule", + "options": "Fee Schedule", + "insert_after": "column_break_ejcc", + }, + ], } diff --git a/education/patches.txt b/education/patches.txt index a0f10d4e..7f596af0 100644 --- a/education/patches.txt +++ b/education/patches.txt @@ -10,4 +10,7 @@ education.patches.v15_0.fees_student_email education.patches.v15_0.student_role_desk_access education.patches.v15_0.sales_invoice_student_field education.patches.v15_0.create_parent_assessment_group -education.patches.v15_0.student_role_permission_sales_invoice \ No newline at end of file +education.patches.v15_0.student_role_permission_sales_invoice +education.patches.v15_0.sales_order_student_field +education.patches.v15_0.fee_schedule_status_update #28-03-2024 +education.patches.v15_0.create_fee_component_item_group \ No newline at end of file diff --git a/education/patches/v15_0/create_fee_component_item_group.py b/education/patches/v15_0/create_fee_component_item_group.py new file mode 100644 index 00000000..77dba603 --- /dev/null +++ b/education/patches/v15_0/create_fee_component_item_group.py @@ -0,0 +1,13 @@ +import frappe + + +def execute(): + if not frappe.db.exists("Item Group", "Fee Component"): + frappe.get_doc( + { + "doctype": "Item Group", + "item_group_name": "Fee Component", + "parent_item_group": "All Item Groups", + "is_group": 0, + } + ).insert() diff --git a/education/patches/v15_0/fee_schedule_status_update.py b/education/patches/v15_0/fee_schedule_status_update.py new file mode 100644 index 00000000..aa448efe --- /dev/null +++ b/education/patches/v15_0/fee_schedule_status_update.py @@ -0,0 +1,20 @@ +import frappe + + +def execute(): + status_map = { + "Successful": "Invoice Created", + "In Process": "In Process", + "Failed": "Fee Creation Failed", + } + fee_schedules = frappe.get_all( + "Fee Schedule", filters={"docstatus": 1}, fields=["name", "fee_creation_status"] + ) + for fee_schedule in fee_schedules: + frappe.db.set_value( + "Fee Schedule", + fee_schedule.name, + "status", + status_map.get(fee_schedule.fee_creation_status, "Invoice Pending"), + update_modified=False, + ) diff --git a/education/patches/v15_0/sales_order_student_field.py b/education/patches/v15_0/sales_order_student_field.py new file mode 100644 index 00000000..591917fb --- /dev/null +++ b/education/patches/v15_0/sales_order_student_field.py @@ -0,0 +1,9 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from education.install import get_custom_fields + + +def execute(): + sales_order_fields = get_custom_fields()["Sales Order"] + sales_order_custom_fields = {"Sales Order": sales_order_fields} + create_custom_fields(sales_order_custom_fields)