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)