feat(Education): added form dashboards and refactored custom buttons for better linking (#22727)

* feat: add form dashboards to all forms in Education Module

* feat: Add Course to Programs button in Course DocType

* refactor: custom buttons in Education module forms

* feat: buttons to add topic, article, quiz to their respective parent doctypes

* feat: add charts to form dashboards

* fix: code cleanup
This commit is contained in:
Rucha Mahabal
2020-07-23 15:57:27 +05:30
committed by GitHub
parent 2826fd3c20
commit 5142fc9365
37 changed files with 1249 additions and 699 deletions

View File

@@ -0,0 +1,25 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'academic_term',
'transactions': [
{
'label': _('Student'),
'items': ['Student Applicant', 'Student Group', 'Student Log']
},
{
'label': _('Fee'),
'items': ['Fees', 'Fee Schedule', 'Fee Structure']
},
{
'label': _('Program'),
'items': ['Program Enrollment']
},
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
}
]
}

View File

@@ -1,10 +1,2 @@
frappe.ui.form.on("Academic Year", "refresh", function(frm) { frappe.ui.form.on("Academic Year", {
if(!frm.doc.__islocal) {
frm.add_custom_button(__("Student Group"), function() {
frappe.route_options = {
academic_year: frm.doc.name
}
frappe.set_route("List", "Student Group");
});
}
}); });

View File

@@ -0,0 +1,25 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'academic_year',
'transactions': [
{
'label': _('Student'),
'items': ['Student Admission', 'Student Applicant', 'Student Group', 'Student Log']
},
{
'label': _('Fee'),
'items': ['Fees', 'Fee Schedule', 'Fee Structure']
},
{
'label': _('Academic Term and Program'),
'items': ['Academic Term', 'Program Enrollment']
},
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
}
]
}

View File

@@ -3,6 +3,54 @@
frappe.ui.form.on('Article', { frappe.ui.form.on('Article', {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) {
frm.add_custom_button(__('Add to Topics'), function() {
frm.trigger('add_article_to_topics');
}, __('Action'));
}
},
add_article_to_topics: function(frm) {
get_topics_without_article(frm.doc.name).then(r => {
if (r.message.length) {
frappe.prompt([
{
fieldname: 'topics',
label: __('Topics'),
fieldtype: 'MultiSelectPills',
get_data: function() {
return r.message;
}
}
],
function(data) {
frappe.call({
method: 'erpnext.education.doctype.topic.topic.add_content_to_topics',
args: {
'content_type': 'Article',
'content': frm.doc.name,
'topics': data.topics,
},
callback: function(r) {
if (!r.exc) {
frm.reload_doc();
}
},
freeze: true,
freeze_message: __('...Adding Article to Topics')
});
}, __('Add Article to Topics'), __('Add'));
} else {
frappe.msgprint(__('This article is already added to the existing topics'));
}
});
} }
}); });
let get_topics_without_article = function(article) {
return frappe.call({
type: 'GET',
method: 'erpnext.education.doctype.article.article.get_topics_without_article',
args: {'article': article}
});
};

View File

@@ -7,9 +7,15 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class Article(Document): class Article(Document):
def get_article(self): def get_article(self):
pass pass
@frappe.whitelist()
def get_topics_without_article(article):
data = []
for entry in frappe.db.get_all('Topic'):
topic = frappe.get_doc('Topic', entry.name)
topic_contents = [tc.content for tc in topic.topic_content]
if not topic_contents or article not in topic_contents:
data.append(topic.name)
return data

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'assessment_group',
'transactions': [
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
}
]
}

View File

@@ -2,9 +2,9 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Assessment Plan", { frappe.ui.form.on('Assessment Plan', {
onload: function(frm) { onload: function(frm) {
frm.set_query("assessment_group", function(doc, cdt, cdn) { frm.set_query('assessment_group', function(doc, cdt, cdn) {
return{ return{
filters: { filters: {
'is_group': 0 'is_group': 0
@@ -22,20 +22,20 @@ frappe.ui.form.on("Assessment Plan", {
refresh: function(frm) { refresh: function(frm) {
if (frm.doc.docstatus == 1) { if (frm.doc.docstatus == 1) {
frm.add_custom_button(__("Assessment Result"), function() { frm.add_custom_button(__('Assessment Result Tool'), function() {
frappe.route_options = { frappe.route_options = {
assessment_plan: frm.doc.name, assessment_plan: frm.doc.name,
student_group: frm.doc.student_group student_group: frm.doc.student_group
} }
frappe.set_route("Form", "Assessment Result Tool"); frappe.set_route('Form', 'Assessment Result Tool');
}); }, __('Tools'));
} }
}, },
course: function(frm) { course: function(frm) {
if (frm.doc.course && frm.doc.maximum_assessment_score) { if (frm.doc.course && frm.doc.maximum_assessment_score) {
frappe.call({ frappe.call({
method: "erpnext.education.api.get_assessment_criteria", method: 'erpnext.education.api.get_assessment_criteria',
args: { args: {
course: frm.doc.course course: frm.doc.course
}, },
@@ -43,12 +43,12 @@ frappe.ui.form.on("Assessment Plan", {
if (r.message) { if (r.message) {
frm.doc.assessment_criteria = []; frm.doc.assessment_criteria = [];
$.each(r.message, function(i, d) { $.each(r.message, function(i, d) {
var row = frappe.model.add_child(frm.doc, "Assessment Plan Criteria", "assessment_criteria"); var row = frappe.model.add_child(frm.doc, 'Assessment Plan Criteria', 'assessment_criteria');
row.assessment_criteria = d.assessment_criteria; row.assessment_criteria = d.assessment_criteria;
row.maximum_score = d.weightage / 100 * frm.doc.maximum_assessment_score; row.maximum_score = d.weightage / 100 * frm.doc.maximum_assessment_score;
}); });
} }
refresh_field("assessment_criteria"); refresh_field('assessment_criteria');
} }
}); });
@@ -56,6 +56,6 @@ frappe.ui.form.on("Assessment Plan", {
}, },
maximum_assessment_score: function(frm) { maximum_assessment_score: function(frm) {
frm.trigger("course"); frm.trigger('course');
} }
}); });

View File

@@ -6,12 +6,16 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'assessment_plan', 'fieldname': 'assessment_plan',
'non_standard_fieldnames': {
},
'transactions': [ 'transactions': [
{ {
'label': _('Assessment'), 'label': _('Assessment'),
'items': ['Assessment Result'] 'items': ['Assessment Result']
} }
],
'reports': [
{
'label': _('Report'),
'items': ['Assessment Plan Status']
}
] ]
} }

View File

@@ -1,7 +1,13 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Assessment Result", { frappe.ui.form.on('Assessment Result', {
refresh: function(frm) {
if (!frm.doc.__islocal) {
frm.trigger('setup_chart');
}
},
onload: function(frm) { onload: function(frm) {
frm.set_query('assessment_plan', function(){ frm.set_query('assessment_plan', function(){
return { return {
@@ -15,7 +21,7 @@ frappe.ui.form.on("Assessment Result", {
assessment_plan: function(frm) { assessment_plan: function(frm) {
if (frm.doc.assessment_plan) { if (frm.doc.assessment_plan) {
frappe.call({ frappe.call({
method: "erpnext.education.api.get_assessment_details", method: 'erpnext.education.api.get_assessment_details',
args: { args: {
assessment_plan: frm.doc.assessment_plan assessment_plan: frm.doc.assessment_plan
}, },
@@ -23,40 +29,75 @@ frappe.ui.form.on("Assessment Result", {
if (r.message) { if (r.message) {
frm.doc.details = []; frm.doc.details = [];
$.each(r.message, function(i, d) { $.each(r.message, function(i, d) {
var row = frappe.model.add_child(frm.doc, "Assessment Result Detail", "details"); var row = frappe.model.add_child(frm.doc, 'Assessment Result Detail', 'details');
row.assessment_criteria = d.assessment_criteria; row.assessment_criteria = d.assessment_criteria;
row.maximum_score = d.maximum_score; row.maximum_score = d.maximum_score;
}); });
} }
refresh_field("details"); refresh_field('details');
} }
}); });
} }
},
setup_chart: function(frm) {
let labels = [];
let maximum_scores = [];
let scores = [];
$.each(frm.doc.details, function(_i, e) {
labels.push(e.assessment_criteria);
maximum_scores.push(e.maximum_score);
scores.push(e.score);
});
if (labels.length && maximum_scores.length && scores.length) {
frm.dashboard.chart_area.empty().removeClass('hidden');
new frappe.Chart('.form-graph', {
title: 'Assessment Results',
data: {
labels: labels,
datasets: [
{
name: 'Maximum Score',
chartType: 'bar',
values: maximum_scores,
},
{
name: 'Score Obtained',
chartType: 'bar',
values: scores,
}
]
},
colors: ['#4CA746', '#98D85B'],
type: 'bar'
});
}
} }
}); });
frappe.ui.form.on("Assessment Result Detail", { frappe.ui.form.on('Assessment Result Detail', {
score: function(frm, cdt, cdn) { score: function(frm, cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if(!d.maximum_score || !frm.doc.grading_scale) { if(!d.maximum_score || !frm.doc.grading_scale) {
d.score = ""; d.score = '';
frappe.throw(__("Please fill in all the details to generate Assessment Result.")); frappe.throw(__('Please fill in all the details to generate Assessment Result.'));
} }
if (d.score > d.maximum_score) { if (d.score > d.maximum_score) {
frappe.throw(__("Score cannot be greater than Maximum Score")); frappe.throw(__('Score cannot be greater than Maximum Score'));
} }
else { else {
frappe.call({ frappe.call({
method: "erpnext.education.api.get_grade", method: 'erpnext.education.api.get_grade',
args: { args: {
grading_scale: frm.doc.grading_scale, grading_scale: frm.doc.grading_scale,
percentage: ((d.score/d.maximum_score) * 100) percentage: ((d.score/d.maximum_score) * 100)
}, },
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
frappe.model.set_value(cdt, cdn, "grade", r.message); frappe.model.set_value(cdt, cdn, 'grade', r.message);
} }
} }
}); });

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'reports': [
{
'label': _('Reports'),
'items': ['Final Assessment Grades', 'Course wise Assessment Report']
}
]
}

View File

@@ -1,41 +1,60 @@
frappe.ui.form.on("Course", "refresh", function(frm) { frappe.ui.form.on('Course', {
if(!cur_frm.doc.__islocal) { refresh: function(frm) {
frm.add_custom_button(__("Program"), function() { if (!cur_frm.doc.__islocal) {
frappe.route_options = { frm.add_custom_button(__('Add to Programs'), function() {
"Program Course.course": frm.doc.name frm.trigger('add_course_to_programs')
} }, __('Action'));
frappe.set_route("List", "Program"); }
});
frm.add_custom_button(__("Student Group"), function() { frm.set_query('default_grading_scale', function(){
frappe.route_options = { return {
course: frm.doc.name filters: {
docstatus: 1
}
} }
frappe.set_route("List", "Student Group");
}); });
},
frm.add_custom_button(__("Course Schedule"), function() { add_course_to_programs: function(frm) {
frappe.route_options = { get_programs_without_course(frm.doc.name).then(r => {
course: frm.doc.name if (r.message.length) {
frappe.prompt([
{
fieldname: 'programs',
label: __('Programs'),
fieldtype: 'MultiSelectPills',
get_data: function() {
return r.message;
}
},
{
fieldtype: 'Check',
label: __('Is Mandatory'),
fieldname: 'mandatory',
}
],
function(data) {
frappe.call({
method: 'erpnext.education.doctype.course.course.add_course_to_programs',
args: {
'course': frm.doc.name,
'programs': data.programs,
'mandatory': data.mandatory
},
callback: function(r) {
if (!r.exc) {
frm.reload_doc();
}
},
freeze: true,
freeze_message: __('...Adding Course to Programs')
})
}, __('Add Course to Programs'), __('Add'));
} else {
frappe.msgprint(__('This course is already added to the existing programs'));
} }
frappe.set_route("List", "Course Schedule");
});
frm.add_custom_button(__("Assessment Plan"), function() {
frappe.route_options = {
course: frm.doc.name
}
frappe.set_route("List", "Assessment Plan");
}); });
} }
frm.set_query('default_grading_scale', function(){
return {
filters: {
docstatus: 1
}
}
});
}); });
frappe.ui.form.on('Course Topic', { frappe.ui.form.on('Course Topic', {
@@ -50,3 +69,11 @@ frappe.ui.form.on('Course Topic', {
}; };
} }
}); });
let get_programs_without_course = function(course) {
return frappe.call({
type: 'GET',
method: 'erpnext.education.doctype.course.course.get_programs_without_course',
args: {'course': course}
});
}

View File

@@ -4,6 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
@@ -17,12 +18,39 @@ class Course(Document):
for criteria in self.assessment_criteria: for criteria in self.assessment_criteria:
total_weightage += criteria.weightage or 0 total_weightage += criteria.weightage or 0
if total_weightage != 100: if total_weightage != 100:
frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%")) frappe.throw(_('Total Weightage of all Assessment Criteria must be 100%'))
def get_topics(self): def get_topics(self):
topic_data= [] topic_data= []
for topic in self.topics: for topic in self.topics:
topic_doc = frappe.get_doc("Topic", topic.topic) topic_doc = frappe.get_doc('Topic', topic.topic)
if topic_doc.topic_content: if topic_doc.topic_content:
topic_data.append(topic_doc) topic_data.append(topic_doc)
return topic_data return topic_data
@frappe.whitelist()
def add_course_to_programs(course, programs, mandatory=False):
programs = json.loads(programs)
for entry in programs:
program = frappe.get_doc('Program', entry)
program.append('courses', {
'course': course,
'course_name': course,
'mandatory': mandatory
})
program.flags.ignore_mandatory = True
program.save()
frappe.db.commit()
frappe.msgprint(_('Course {0} has been added to all the selected programs successfully.').format(frappe.bold(course)),
title=_('Programs updated'), indicator='green')
@frappe.whitelist()
def get_programs_without_course(course):
data = []
for entry in frappe.db.get_all('Program'):
program = frappe.get_doc('Program', entry.name)
courses = [c.course for c in program.courses]
if not courses or course not in courses:
data.append(program.name)
return data

View File

@@ -6,12 +6,10 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'course', 'fieldname': 'course',
'non_standard_fieldnames': {
},
'transactions': [ 'transactions': [
{ {
'label': _('Course'), 'label': _('Program and Course'),
'items': ['Course Enrollment', 'Course Schedule'] 'items': ['Program', 'Course Enrollment', 'Course Schedule']
}, },
{ {
'label': _('Student'), 'label': _('Student'),
@@ -19,7 +17,7 @@ def get_data():
}, },
{ {
'label': _('Assessment'), 'label': _('Assessment'),
'items': ['Assessment Plan'] 'items': ['Assessment Plan', 'Assessment Result']
}, },
] ]
} }

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'enrollment',
'transactions': [
{
'label': _('Activity'),
'items': ['Course Activity', 'Quiz Activity']
}
]
}

View File

@@ -4,13 +4,13 @@ cur_frm.add_fetch("student_group", "course", "course")
frappe.ui.form.on("Course Schedule", { frappe.ui.form.on("Course Schedule", {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) { if (!frm.doc.__islocal) {
frm.add_custom_button(__("Attendance"), function() { frm.add_custom_button(__("Mark Attendance"), function() {
frappe.route_options = { frappe.route_options = {
based_on: "Course Schedule", based_on: "Course Schedule",
course_schedule: frm.doc.name course_schedule: frm.doc.name
} }
frappe.set_route("Form", "Student Attendance Tool"); frappe.set_route("Form", "Student Attendance Tool");
}); }).addClass("btn-primary");
} }
} }
}); });

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'course_schedule',
'transactions': [
{
'label': _('Attendance'),
'items': ['Student Attendance']
}
]
}

View File

@@ -3,13 +3,13 @@
frappe.ui.form.on('Fee Schedule', { frappe.ui.form.on('Fee Schedule', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("fee_structure", "receivable_account", "receivable_account"); frm.add_fetch('fee_structure', 'receivable_account', 'receivable_account');
frm.add_fetch("fee_structure", "income_account", "income_account"); frm.add_fetch('fee_structure', 'income_account', 'income_account');
frm.add_fetch("fee_structure", "cost_center", "cost_center"); frm.add_fetch('fee_structure', 'cost_center', 'cost_center');
}, },
onload: function(frm) { onload: function(frm) {
frm.set_query("receivable_account", function(doc) { frm.set_query('receivable_account', function(doc) {
return { return {
filters: { filters: {
'account_type': 'Receivable', 'account_type': 'Receivable',
@@ -18,7 +18,8 @@ frappe.ui.form.on('Fee Schedule', {
} }
}; };
}); });
frm.set_query("income_account", function(doc) {
frm.set_query('income_account', function(doc) {
return { return {
filters: { filters: {
'account_type': 'Income Account', 'account_type': 'Income Account',
@@ -27,57 +28,59 @@ frappe.ui.form.on('Fee Schedule', {
} }
}; };
}); });
frm.set_query("student_group", "student_groups", function() {
frm.set_query('student_group', 'student_groups', function() {
return { return {
"program": frm.doc.program, 'program': frm.doc.program,
"academic_term": frm.doc.academic_term, 'academic_term': frm.doc.academic_term,
"academic_year": frm.doc.academic_year, 'academic_year': frm.doc.academic_year,
"disabled": 0 'disabled': 0
}; };
}); });
frappe.realtime.on("fee_schedule_progress", function(data) {
frappe.realtime.on('fee_schedule_progress', function(data) {
if (data.reload && data.reload === 1) { if (data.reload && data.reload === 1) {
frm.reload_doc(); frm.reload_doc();
} }
if (data.progress) { if (data.progress) {
let progress_bar = $(cur_frm.dashboard.progress_area).find(".progress-bar"); let progress_bar = $(cur_frm.dashboard.progress_area).find('.progress-bar');
if (progress_bar) { if (progress_bar) {
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped"); $(progress_bar).removeClass('progress-bar-danger').addClass('progress-bar-success progress-bar-striped');
$(progress_bar).css("width", data.progress+"%"); $(progress_bar).css('width', data.progress+'%');
} }
} }
}); });
}, },
refresh: function(frm) { refresh: function(frm) {
if(!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info && if (!frm.doc.__islocal && frm.doc.__onload && frm.doc.__onload.dashboard_info &&
frm.doc.fee_creation_status=="Successful") { frm.doc.fee_creation_status === 'Successful') {
var info = frm.doc.__onload.dashboard_info; var info = frm.doc.__onload.dashboard_info;
frm.dashboard.add_indicator(__('Total Collected: {0}', [format_currency(info.total_paid, frm.dashboard.add_indicator(__('Total Collected: {0}', [format_currency(info.total_paid,
info.currency)]), 'blue'); info.currency)]), 'blue');
frm.dashboard.add_indicator(__('Total Outstanding: {0}', [format_currency(info.total_unpaid, frm.dashboard.add_indicator(__('Total Outstanding: {0}', [format_currency(info.total_unpaid,
info.currency)]), info.total_unpaid ? 'orange' : 'green'); info.currency)]), info.total_unpaid ? 'orange' : 'green');
} }
if (frm.doc.fee_creation_status=="In Process") { if (frm.doc.fee_creation_status === 'In Process') {
frm.dashboard.add_progress("Fee Creation Status", "0"); frm.dashboard.add_progress('Fee Creation Status', '0');
} }
if (frm.doc.docstatus==1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status == "Failed") { if (frm.doc.docstatus === 1 && !frm.doc.fee_creation_status || frm.doc.fee_creation_status === 'Failed') {
frm.add_custom_button(__('Create Fees'), function() { frm.add_custom_button(__('Create Fees'), function() {
frappe.call({ frappe.call({
method: "create_fees", method: 'create_fees',
doc: frm.doc, doc: frm.doc,
callback: function() { callback: function() {
frm.refresh(); frm.refresh();
} }
}); });
}, "fa fa-play", "btn-success"); }).addClass('btn-primary');;
} }
if (frm.doc.fee_creation_status == "Successful") { if (frm.doc.fee_creation_status === 'Successful') {
frm.add_custom_button(__("View Fees Records"), function() { frm.add_custom_button(__('View Fees Records'), function() {
frappe.route_options = { frappe.route_options = {
fee_schedule: frm.doc.name fee_schedule: frm.doc.name
}; };
frappe.set_route("List", "Fees"); frappe.set_route('List', 'Fees');
}); });
} }
@@ -86,35 +89,35 @@ frappe.ui.form.on('Fee Schedule', {
fee_structure: function(frm) { fee_structure: function(frm) {
if (frm.doc.fee_structure) { if (frm.doc.fee_structure) {
frappe.call({ frappe.call({
method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure", method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_fee_structure',
args: { args: {
"target_doc": frm.doc.name, 'target_doc': frm.doc.name,
"source_name": frm.doc.fee_structure 'source_name': frm.doc.fee_structure
}, },
callback: function(r) { callback: function(r) {
var doc = frappe.model.sync(r.message); 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", { frappe.ui.form.on('Fee Schedule Student Group', {
student_group: function(frm, cdt, cdn) { student_group: function(frm, cdt, cdn) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
if (row.student_group && frm.doc.academic_year) { if (row.student_group && frm.doc.academic_year) {
frappe.call({ frappe.call({
method: "erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students", method: 'erpnext.education.doctype.fee_schedule.fee_schedule.get_total_students',
args: { args: {
"student_group": row.student_group, 'student_group': row.student_group,
"academic_year": frm.doc.academic_year, 'academic_year': frm.doc.academic_year,
"academic_term": frm.doc.academic_term, 'academic_term': frm.doc.academic_term,
"student_category": frm.doc.student_category 'student_category': frm.doc.student_category
}, },
callback: function(r) { callback: function(r) {
if(!r.exc) { if (!r.exc) {
frappe.model.set_value(cdt, cdn, "total_students", r.message); frappe.model.set_value(cdt, cdn, 'total_students', r.message);
} }
} }
}); });

View File

@@ -0,0 +1,13 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
def get_data():
return {
'fieldname': 'fee_schedule',
'transactions': [
{
'items': ['Fees']
}
]
}

View File

@@ -3,21 +3,21 @@
frappe.ui.form.on('Fee Structure', { frappe.ui.form.on('Fee Structure', {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("company", "default_receivable_account", "receivable_account"); frm.add_fetch('company', 'default_receivable_account', 'receivable_account');
frm.add_fetch("company", "default_income_account", "income_account"); frm.add_fetch('company', 'default_income_account', 'income_account');
frm.add_fetch("company", "cost_center", "cost_center"); frm.add_fetch('company', 'cost_center', 'cost_center');
}, },
onload: function(frm) { onload: function(frm) {
frm.set_query("academic_term", function() { frm.set_query('academic_term', function() {
return { return {
"filters": { 'filters': {
"academic_year": frm.doc.academic_year 'academic_year': frm.doc.academic_year
} }
}; };
}); });
frm.set_query("receivable_account", function(doc) { frm.set_query('receivable_account', function(doc) {
return { return {
filters: { filters: {
'account_type': 'Receivable', 'account_type': 'Receivable',
@@ -26,7 +26,7 @@ frappe.ui.form.on('Fee Structure', {
} }
}; };
}); });
frm.set_query("income_account", function(doc) { frm.set_query('income_account', function(doc) {
return { return {
filters: { filters: {
'account_type': 'Income Account', 'account_type': 'Income Account',
@@ -38,27 +38,27 @@ frappe.ui.form.on('Fee Structure', {
}, },
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Create Fee Schedule'), function() { frm.add_custom_button(__('Create Fee Schedule'), function() {
frm.events.make_fee_schedule(frm); frm.events.make_fee_schedule(frm);
}); }).addClass('btn-primary');
} }
}, },
make_fee_schedule: function(frm) { make_fee_schedule: function(frm) {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule", method: 'erpnext.education.doctype.fee_structure.fee_structure.make_fee_schedule',
frm: frm frm: frm
}); });
} }
}); });
frappe.ui.form.on("Fee Component", { frappe.ui.form.on('Fee Component', {
amount: function(frm) { amount: function(frm) {
var total_amount = 0; var total_amount = 0;
for(var i=0;i<frm.doc.components.length;i++) { for (var i=0;i<frm.doc.components.length;i++) {
total_amount += frm.doc.components[i].amount; total_amount += frm.doc.components[i].amount;
} }
frm.set_value("total_amount", total_amount); frm.set_value('total_amount', total_amount);
} }
}); });

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'fee_structure',
'transactions': [
{
'label': _('Fee'),
'items': ['Fees', 'Fee Schedule']
}
]
}

View File

@@ -0,0 +1,20 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'grading_scale',
'non_standard_fieldnames': {
'Course': 'default_grading_scale'
},
'transactions': [
{
'label': _('Course'),
'items': ['Course']
},
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
}
]
}

View File

@@ -3,8 +3,8 @@ cur_frm.add_fetch("employee", "image", "image");
frappe.ui.form.on("Instructor", { frappe.ui.form.on("Instructor", {
employee: function(frm) { employee: function(frm) {
if(!frm.doc.employee) return; if (!frm.doc.employee) return;
frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (d) => { frappe.db.get_value("Employee", {name: frm.doc.employee}, "company", (d) => {
frm.set_query("department", function() { frm.set_query("department", function() {
return { return {
"filters": { "filters": {
@@ -22,30 +22,16 @@ frappe.ui.form.on("Instructor", {
}); });
}, },
refresh: function(frm) { refresh: function(frm) {
if(!frm.doc.__islocal) { if (!frm.doc.__islocal) {
frm.add_custom_button(__("Student Group"), function() {
frappe.route_options = {
instructor: frm.doc.name
}
frappe.set_route("List", "Student Group");
});
frm.add_custom_button(__("Course Schedule"), function() {
frappe.route_options = {
instructor: frm.doc.name
}
frappe.set_route("List", "Course Schedule");
});
frm.add_custom_button(__("As Examiner"), function() { frm.add_custom_button(__("As Examiner"), function() {
frappe.route_options = { frappe.new_doc("Assessment Plan", {
examiner: frm.doc.name examiner: frm.doc.name
} });
frappe.set_route("List", "Assessment Plan");
}, __("Assessment Plan")); }, __("Assessment Plan"));
frm.add_custom_button(__("As Supervisor"), function() { frm.add_custom_button(__("As Supervisor"), function() {
frappe.route_options = { frappe.new_doc("Assessment Plan", {
supervisor: frm.doc.name supervisor: frm.doc.name
} });
frappe.set_route("List", "Assessment Plan");
}, __("Assessment Plan")); }, __("Assessment Plan"));
} }
frm.set_query("employee", function(doc) { frm.set_query("employee", function(doc) {

View File

@@ -30,4 +30,14 @@ class Instructor(Document):
if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'): if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'):
frappe.throw(_("Employee ID is linked with another instructor")) frappe.throw(_("Employee ID is linked with another instructor"))
def get_timeline_data(doctype, name):
"""Return timeline for course schedule"""
return dict(frappe.db.sql(
"""
SELECT unix_timestamp(`schedule_date`), count(*)
FROM `tabCourse Schedule`
WHERE
instructor=%s and
`schedule_date` > date_sub(curdate(), interval 1 year)
GROUP BY schedule_date
""", name))

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'heatmap': True,
'heatmap_message': _('This is based on the course schedules of this Instructor'),
'fieldname': 'instructor',
'non_standard_fieldnames': {
'Assessment Plan': 'supervisor'
},
'transactions': [
{
'label': _('Course and Assessment'),
'items': ['Course Schedule', 'Assessment Plan']
},
{
'label': _('Students'),
'items': ['Student Group']
}
]
}

View File

@@ -10,11 +10,15 @@ def get_data():
}, },
{ {
'label': _('Student Activity'), 'label': _('Student Activity'),
'items': ['Student Group' ] 'items': ['Student Group', 'Student Log']
}, },
{ {
'label': _('Fee'), 'label': _('Fee'),
'items': ['Fees','Fee Structure'] 'items': ['Fees','Fee Structure', 'Fee Schedule']
},
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
} }
] ]
} }

View File

@@ -0,0 +1,19 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'program_enrollment',
'transactions': [
{
'label': _('Course and Fee'),
'items': ['Course Enrollment', 'Fees']
}
],
'reports': [
{
'label': _('Report'),
'items': ['Student and Guardian Contact Details']
}
]
}

View File

@@ -3,11 +3,17 @@
frappe.ui.form.on('Quiz', { frappe.ui.form.on('Quiz', {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) {
frm.add_custom_button(__('Add to Topics'), function() {
frm.trigger('add_quiz_to_topics');
}, __('Action'));
}
}, },
validate: function(frm){ validate: function(frm){
frm.events.check_duplicate_question(frm.doc.question); frm.events.check_duplicate_question(frm.doc.question);
}, },
check_duplicate_question: function(questions_data){ check_duplicate_question: function(questions_data){
var questions = []; var questions = [];
questions_data.forEach(function(q){ questions_data.forEach(function(q){
@@ -15,7 +21,51 @@ frappe.ui.form.on('Quiz', {
}); });
var questions_set = new Set(questions); var questions_set = new Set(questions);
if (questions.length != questions_set.size) { if (questions.length != questions_set.size) {
frappe.throw(__("The question cannot be duplicate")); frappe.throw(__('The question cannot be duplicate'));
} }
},
add_quiz_to_topics: function(frm) {
get_topics_without_quiz(frm.doc.name).then(r => {
if (r.message.length) {
frappe.prompt([
{
fieldname: 'topics',
label: __('Topics'),
fieldtype: 'MultiSelectPills',
get_data: function() {
return r.message;
}
}
],
function(data) {
frappe.call({
method: 'erpnext.education.doctype.topic.topic.add_content_to_topics',
args: {
'content_type': 'Quiz',
'content': frm.doc.name,
'topics': data.topics,
},
callback: function(r) {
if (!r.exc) {
frm.reload_doc();
}
},
freeze: true,
freeze_message: __('...Adding Quiz to Topics')
});
}, __('Add Quiz to Topics'), __('Add'));
} else {
frappe.msgprint(__('This quiz is already added to the existing topics'));
}
});
} }
}); });
let get_topics_without_quiz = function(quiz) {
return frappe.call({
type: 'GET',
method: 'erpnext.education.doctype.quiz.quiz.get_topics_without_quiz',
args: {'quiz': quiz}
});
};

View File

@@ -4,6 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
@@ -59,3 +60,12 @@ def compare_list_elementwise(*args):
except TypeError: except TypeError:
frappe.throw(_("Compare List function takes on list arguments")) frappe.throw(_("Compare List function takes on list arguments"))
@frappe.whitelist()
def get_topics_without_quiz(quiz):
data = []
for entry in frappe.db.get_all('Topic'):
topic = frappe.get_doc('Topic', entry.name)
topic_contents = [tc.content for tc in topic.topic_content]
if not topic_contents or quiz not in topic_contents:
data.append(topic.name)
return data

View File

@@ -1,10 +1,2 @@
frappe.ui.form.on("Room", "refresh", function(frm) { frappe.ui.form.on("Room", {
if(!cur_frm.doc.__islocal) {
frm.add_custom_button(__("Course Schedule"), function() {
frappe.route_options = {
room: frm.doc.name
}
frappe.set_route("List", "Course Schedule");
});
}
}); });

View File

@@ -0,0 +1,19 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'room',
'transactions': [
{
'label': _('Course'),
'items': ['Course Schedule']
},
{
'label': _('Assessment'),
'items': ['Assessment Plan']
}
]
}

View File

@@ -0,0 +1,12 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'reports': [
{
'label': _('Reports'),
'items': ['Student Monthly Attendance Sheet', 'Student Batch-Wise Attendance']
}
]
}

View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'student_category',
'transactions': [
{
'label': _('Fee'),
'items': ['Fee Structure', 'Fee Schedule', 'Fees']
}
]
}

View File

@@ -1,18 +1,18 @@
cur_frm.add_fetch("student", "title", "student_name"); cur_frm.add_fetch('student', 'title', 'student_name');
frappe.ui.form.on("Student Group", { frappe.ui.form.on('Student Group', {
onload: function(frm) { onload: function(frm) {
frm.set_query("academic_term", function() { frm.set_query('academic_term', function() {
return { return {
"filters": { filters: {
"academic_year": (frm.doc.academic_year) 'academic_year': (frm.doc.academic_year)
} }
}; };
}); });
if (!frm.__islocal) { if (!frm.__islocal) {
frm.set_query("student", "students", function() { frm.set_query('student', 'students', function() {
return{ return{
query: "erpnext.education.doctype.student_group.student_group.fetch_students", query: 'erpnext.education.doctype.student_group.student_group.fetch_students',
filters: { filters: {
'academic_year': frm.doc.academic_year, 'academic_year': frm.doc.academic_year,
'group_based_on': frm.doc.group_based_on, 'group_based_on': frm.doc.group_based_on,
@@ -30,87 +30,86 @@ frappe.ui.form.on("Student Group", {
refresh: function(frm) { refresh: function(frm) {
if (!frm.doc.__islocal) { if (!frm.doc.__islocal) {
frm.add_custom_button(__("Attendance"), function() {
frappe.route_options = { frm.add_custom_button(__('Add Guardians to Email Group'), function() {
based_on: "Student Group",
student_group: frm.doc.name
}
frappe.set_route("List", "Student Attendance Tool");
});
frm.add_custom_button(__("Course Schedule"), function() {
frappe.route_options = {
student_group: frm.doc.name
}
frappe.set_route("List", "Course Schedule");
});
frm.add_custom_button(__("Assessment Plan"), function() {
frappe.route_options = {
student_group: frm.doc.name
}
frappe.set_route("List", "Assessment Plan");
});
frm.add_custom_button(__("Update Email Group"), function() {
frappe.call({ frappe.call({
method: "erpnext.education.api.update_email_group", method: 'erpnext.education.api.update_email_group',
args: { args: {
"doctype": "Student Group", 'doctype': 'Student Group',
"name": frm.doc.name 'name': frm.doc.name
} }
}); });
}); }, __('Actions'));
frm.add_custom_button(__("Newsletter"), function() {
frm.add_custom_button(__('Student Attendance Tool'), function() {
frappe.route_options = { frappe.route_options = {
"Newsletter Email Group.email_group": frm.doc.name based_on: 'Student Group',
student_group: frm.doc.name
} }
frappe.set_route("List", "Newsletter"); frappe.set_route('Form', 'Student Attendance Tool', 'Student Attendance Tool');
}); }, __('Tools'));
frm.add_custom_button(__('Course Scheduling Tool'), function() {
frappe.route_options = {
student_group: frm.doc.name
}
frappe.set_route('Form', 'Course Scheduling Tool', 'Course Scheduling Tool');
}, __('Tools'));
frm.add_custom_button(__('Newsletter'), function() {
frappe.route_options = {
'Newsletter Email Group.email_group': frm.doc.name
}
frappe.set_route('List', 'Newsletter');
}, __('View'));
} }
}, },
group_based_on: function(frm) { group_based_on: function(frm) {
if (frm.doc.group_based_on == "Batch") { if (frm.doc.group_based_on == 'Batch') {
frm.doc.course = null; frm.doc.course = null;
frm.set_df_property('program', 'reqd', 1); frm.set_df_property('program', 'reqd', 1);
frm.set_df_property('course', 'reqd', 0); frm.set_df_property('course', 'reqd', 0);
} }
else if (frm.doc.group_based_on == "Course") { else if (frm.doc.group_based_on == 'Course') {
frm.set_df_property('program', 'reqd', 0); frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 1); frm.set_df_property('course', 'reqd', 1);
} }
else if (frm.doc.group_based_on == "Activity") { else if (frm.doc.group_based_on == 'Activity') {
frm.set_df_property('program', 'reqd', 0); frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 0); frm.set_df_property('course', 'reqd', 0);
} }
}, },
get_students: function(frm) { get_students: function(frm) {
if (frm.doc.group_based_on == "Batch" || frm.doc.group_based_on == "Course") { if (frm.doc.group_based_on == 'Batch' || frm.doc.group_based_on == 'Course') {
var student_list = []; var student_list = [];
var max_roll_no = 0; var max_roll_no = 0;
$.each(frm.doc.students, function(i,d) { $.each(frm.doc.students, function(_i,d) {
student_list.push(d.student); 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; max_roll_no = d.group_roll_number;
} }
}); });
if(frm.doc.academic_year) { if (frm.doc.academic_year) {
frappe.call({ frappe.call({
method: "erpnext.education.doctype.student_group.student_group.get_students", method: 'erpnext.education.doctype.student_group.student_group.get_students',
args: { args: {
"academic_year": frm.doc.academic_year, 'academic_year': frm.doc.academic_year,
"academic_term": frm.doc.academic_term, 'academic_term': frm.doc.academic_term,
"group_based_on": frm.doc.group_based_on, 'group_based_on': frm.doc.group_based_on,
"program": frm.doc.program, 'program': frm.doc.program,
"batch" : frm.doc.batch, 'batch' : frm.doc.batch,
"student_category" : frm.doc.student_category, 'student_category' : frm.doc.student_category,
"course": frm.doc.course 'course': frm.doc.course
}, },
callback: function(r) { callback: function(r) {
if(r.message) { if (r.message) {
$.each(r.message, function(i, d) { $.each(r.message, function(i, d) {
if(!in_list(student_list, d.student)) { if(!in_list(student_list, d.student)) {
var s = frm.add_child("students"); var s = frm.add_child('students');
s.student = d.student; s.student = d.student;
s.student_name = d.student_name; s.student_name = d.student_name;
if (d.active === 0) { if (d.active === 0) {
@@ -119,16 +118,16 @@ frappe.ui.form.on("Student Group", {
s.group_roll_number = ++max_roll_no; s.group_roll_number = ++max_roll_no;
} }
}); });
refresh_field("students"); refresh_field('students');
frm.save(); frm.save();
} else { } else {
frappe.msgprint(__("Student Group is already updated.")) frappe.msgprint(__('Student Group is already updated.'))
} }
} }
}) })
} }
} else { } else {
frappe.msgprint(__("Select students manually for the Activity based Group")); frappe.msgprint(__('Select students manually for the Activity based Group'));
} }
} }
}); });

View File

@@ -0,0 +1,19 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'student_group',
'transactions': [
{
'label': _('Assessment'),
'items': ['Assessment Plan', 'Assessment Result']
},
{
'label': _('Course'),
'items': ['Course Schedule']
}
]
}

View File

@@ -3,6 +3,53 @@
frappe.ui.form.on('Topic', { frappe.ui.form.on('Topic', {
refresh: function(frm) { refresh: function(frm) {
if (!cur_frm.doc.__islocal) {
frm.add_custom_button(__('Add to Courses'), function() {
frm.trigger('add_topic_to_courses');
}, __('Action'));
}
},
add_topic_to_courses: function(frm) {
get_courses_without_topic(frm.doc.name).then(r => {
if (r.message.length) {
frappe.prompt([
{
fieldname: 'courses',
label: __('Courses'),
fieldtype: 'MultiSelectPills',
get_data: function() {
return r.message;
}
}
],
function(data) {
frappe.call({
method: 'erpnext.education.doctype.topic.topic.add_topic_to_courses',
args: {
'topic': frm.doc.name,
'courses': data.courses
},
callback: function(r) {
if (!r.exc) {
frm.reload_doc();
}
},
freeze: true,
freeze_message: __('...Adding Topic to Courses')
});
}, __('Add Topic to Courses'), __('Add'));
} else {
frappe.msgprint(__('This topic is already added to the existing courses'));
}
});
} }
}); });
let get_courses_without_topic = function(topic) {
return frappe.call({
type: 'GET',
method: 'erpnext.education.doctype.topic.topic.get_courses_without_topic',
args: {'topic': topic}
});
};

View File

@@ -4,6 +4,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
class Topic(Document): class Topic(Document):
@@ -15,3 +17,43 @@ class Topic(Document):
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
return None return None
return content_data return content_data
@frappe.whitelist()
def get_courses_without_topic(topic):
data = []
for entry in frappe.db.get_all('Course'):
course = frappe.get_doc('Course', entry.name)
topics = [t.topic for t in course.topics]
if not topics or topic not in topics:
data.append(course.name)
return data
@frappe.whitelist()
def add_topic_to_courses(topic, courses, mandatory=False):
courses = json.loads(courses)
for entry in courses:
course = frappe.get_doc('Course', entry)
course.append('topics', {
'topic': topic,
'topic_name': topic
})
course.flags.ignore_mandatory = True
course.save()
frappe.db.commit()
frappe.msgprint(_('Topic {0} has been added to all the selected courses successfully.').format(frappe.bold(topic)),
title=_('Courses updated'), indicator='green')
@frappe.whitelist()
def add_content_to_topics(content_type, content, topics):
topics = json.loads(topics)
for entry in topics:
topic = frappe.get_doc('Topic', entry)
topic.append('topic_content', {
'content_type': content_type,
'content': content,
})
topic.flags.ignore_mandatory = True
topic.save()
frappe.db.commit()
frappe.msgprint(_('{0} {1} has been added to all the selected topics successfully.').format(content_type, frappe.bold(content)),
title=_('Topics updated'), indicator='green')