refactored assessment result tool (#10633)
* save the assessment instead of submit
* Added comments in the artool
* remove the cur_frm and message for submitted result
* link field for the assessment result
diff --git a/erpnext/public/js/schools/assessment_result_tool.html b/erpnext/public/js/schools/assessment_result_tool.html
index 3c09ccd..9fc17f7 100644
--- a/erpnext/public/js/schools/assessment_result_tool.html
+++ b/erpnext/public/js/schools/assessment_result_tool.html
@@ -1,44 +1,72 @@
<table class="table table-bordered assessment-result-tool">
- <thead>
- <tr>
- <th style="width: 100px" rowspan="2">Student</th>
- <th style="width: 200px" rowspan="2">Student Name</th>
- {% for c in criteria %}
- <th class="score" style="width: 100px">{{ c.assessment_criteria }}</th>
- {% endfor %}
- <th class="score" style="width: 100px">Total Marks</th>
- <!--criteria-->
- </tr>
- <tr>
- {% for c in criteria %}
- <th class="score" style="width: 100px">{{ c.maximum_score }}</th>
- {% endfor %}
- <th class="score" style="width: 100px">{{max_total_score}}</th>
- </tr>
- </thead>
- <tbody>
- {% for s in students %}
- <tr
- {% if(s.assessment_details) { %} class="text-muted" {% } %}
- data-student="{{s.student}}">
- <td>{{ s.student }}</td>
- <td>{{ s.student_name }}</td>
- {% for c in criteria %}
- <td>
- <input type="text"
- data-max-score="{{c.maximum_score}}"
- data-criteria="{{c.assessment_criteria}}"
- data-student="{{s.student}}"
- {% if(s.assessment_details) { %}
- disabled
- value="{{s.assessment_details[c.assessment_criteria]}}"
- {% } %}/>
- </td>
- {% endfor %}
- <td data-student="{{s.student}}" class="total-score">
- {% if(s.assessment_details) { %} {{s.assessment_details.total_score}} {% } %}
- </td>
- </tr>
- {% endfor %}
- </tbody>
+ <thead>
+ <tr>
+ <th style="width: 90px" rowspan="2">Student</th>
+ <th style="width: 170px" rowspan="2">Student Name</th>
+ {% for c in criteria %}
+ <th class="score" style="width: 100px">{{ c.assessment_criteria }}</th>
+ {% endfor %}
+ <th class="score" style="width: 170px" rowspan="2">Comments</th>
+ <th class="score" style="width: 100px">Total Marks</th>
+ <!--criteria-->
+ </tr>
+ <tr>
+ {% for c in criteria %}
+ <th class="score" style="width: 100px">Score ({{ c.maximum_score }})</th>
+ {% endfor %}
+ <th class="score" style="width: 100px">Score ({{max_total_score}})</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for s in students %}
+ <tr
+ {% if(s.assessment_details && s.docstatus && s.docstatus == 1) { %} class="text-muted" {% } %}
+ data-student="{{s.student}}">
+
+ <td>{{ s.student }}</td>
+ <td>{{ s.student_name }}</td>
+ {% for c in criteria %}
+ <td>
+ <span data-student="{{s.student}}" data-criteria="{{c.assessment_criteria}}" class="student-result-grade badge" >
+ {% if(s.assessment_details) { %}
+ {{s.assessment_details[c.assessment_criteria][1]}}
+ {% } %}
+ </span>
+ <input type="number" class="student-result-data" style="width:70%; float:right;"
+ data-max-score="{{c.maximum_score}}"
+ data-criteria="{{c.assessment_criteria}}"
+ data-student="{{s.student}}"
+ {% if(s.assessment_details && s.docstatus && s.docstatus == 1) { %} disabled {% } %}
+ {% if(s.assessment_details) { %}
+ value="{{s.assessment_details[c.assessment_criteria][0]}}"
+ {% } %}/>
+ </td>
+ {% endfor %}
+ <td>
+ <input type="text" class="result-comment" data-student="{{s.student}}"
+ {% if(s.assessment_details && s.docstatus && s.docstatus == 1) { %} disabled {% } %}
+ {% if(s.assessment_details) { %}
+ value="{{s.assessment_details.comment}}"
+ {% } %}
+ </td>
+ <td>
+ <span data-student="{{s.student}}" class="total-score-grade badge" style="width:30%; float:left;">
+ {% if(s.assessment_details) { %}
+ {{s.assessment_details.total_score[1]}}
+ {% } %}
+ </span>
+ <span data-student="{{s.student}}" class="total-score" style="width:60%; float:center;">
+ {% if(s.assessment_details) { %}
+ {{s.assessment_details.total_score[0]}}
+ {% } %}
+ </span>
+ <span data-student="{{s.student}}" class="total-result-link" style="width: 10%; display:{% if(!s.assessment_details) { %}None{% } %}; float:right;">
+ <a class="btn-open no-decoration" title="Open Link" href="#Form/Assessment Result/{% if(s.assessment_details) { %}{{s.name}}{% } %}">
+ <i class="octicon octicon-arrow-right"></i>
+ </a>
+ </span>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
</table>
\ No newline at end of file
diff --git a/erpnext/schools/api.py b/erpnext/schools/api.py
index ff2da07..41d4a0d 100644
--- a/erpnext/schools/api.py
+++ b/erpnext/schools/api.py
@@ -18,6 +18,7 @@
(program), as_dict=1)
return courses
+
@frappe.whitelist()
def enroll_student(source_name):
"""Creates a Student Record and returns a Program Enrollment.
@@ -40,6 +41,7 @@
frappe.publish_realtime('enroll_student_progress', {"progress": [4, 4]}, user=frappe.session.user)
return program_enrollment
+
@frappe.whitelist()
def check_attendance_records_exist(course_schedule=None, student_group=None, date=None):
"""Check if Attendance Records are made against the specified Course Schedule or Student Group for given date.
@@ -53,6 +55,7 @@
else:
return frappe.get_list("Student Attendance", filters={"student_group": student_group, "date": date})
+
@frappe.whitelist()
def mark_attendance(students_present, students_absent, course_schedule=None, student_group=None, date=None):
"""Creates Multiple Attendance Records.
@@ -76,6 +79,7 @@
frappe.db.commit()
frappe.msgprint(_("Attendance has been marked successfully."))
+
def make_attendance_records(student, student_name, status, course_schedule=None, student_group=None, date=None):
"""Creates/Update Attendance Record.
@@ -103,6 +107,7 @@
student_attendance.status = status
student_attendance.save()
+
@frappe.whitelist()
def get_student_guardians(student):
"""Returns List of Guardians of a Student.
@@ -113,6 +118,7 @@
filters={"parent": student})
return guardians
+
@frappe.whitelist()
def get_student_group_students(student_group, include_inactive=0):
"""Returns List of student, student_name in Student Group.
@@ -127,6 +133,7 @@
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
return students
+
@frappe.whitelist()
def get_fee_structure(program, academic_term=None):
"""Returns Fee Structure.
@@ -138,6 +145,7 @@
"academic_term": academic_term}, 'name', as_dict=True)
return fee_structure[0].name if fee_structure else None
+
@frappe.whitelist()
def get_fee_components(fee_structure):
"""Returns Fee Components.
@@ -148,6 +156,7 @@
fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
return fs
+
@frappe.whitelist()
def get_fee_schedule(program, student_category=None):
"""Returns Fee Schedule.
@@ -159,6 +168,7 @@
filters={"parent": program, "student_category": student_category }, order_by= "idx")
return fs
+
@frappe.whitelist()
def collect_fees(fees, amt):
paid_amount = flt(amt) + flt(frappe.db.get_value("Fees", fees, "paid_amount"))
@@ -167,6 +177,7 @@
frappe.db.set_value("Fees", fees, "outstanding_amount", (total_amount - paid_amount))
return paid_amount
+
@frappe.whitelist()
def get_course_schedule_events(start, end, filters=None):
"""Returns events for Course Schedule Calendar view rendering.
@@ -191,6 +202,7 @@
return data
+
@frappe.whitelist()
def get_assessment_criteria(course):
"""Returns Assessmemt Criteria and their Weightage from Course Master.
@@ -200,22 +212,30 @@
return frappe.get_list("Course Assessment Criteria", \
fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx")
+
@frappe.whitelist()
def get_assessment_students(assessment_plan, student_group):
-
student_list = get_student_group_students(student_group)
for i, student in enumerate(student_list):
result = get_result(student.student, assessment_plan)
if result:
student_result = {}
for d in result.details:
- student_result.update({d.assessment_criteria: cstr(d.score) + " ("+ d.grade + ")"})
- student_result.update({"total_score": cstr(result.total_score) + " (" + result.grade + ")"})
- student.update({'assessment_details': student_result})
+ student_result.update({d.assessment_criteria: [cstr(d.score), d.grade]})
+ student_result.update({
+ "total_score": [cstr(result.total_score), result.grade],
+ "comment": result.comment
+ })
+ student.update({
+ "assessment_details": student_result,
+ "docstatus": result.docstatus,
+ "name": result.name
+ })
else:
student.update({'assessment_details': None})
return student_list
+
@frappe.whitelist()
def get_assessment_details(assessment_plan):
"""Returns Assessment Criteria and Maximum Score from Assessment Plan Master.
@@ -223,7 +243,8 @@
:param Assessment Plan: Assessment Plan
"""
return frappe.get_list("Assessment Plan Criteria", \
- fields=["assessment_criteria", "maximum_score"], filters={"parent": assessment_plan}, order_by= "idx")
+ fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx")
+
@frappe.whitelist()
def get_result(student, assessment_plan):
@@ -232,12 +253,14 @@
:param Student: Student
:param Assessment Plan: Assessment Plan
"""
- results = frappe.get_all("Assessment Result", filters={"student": student, "assessment_plan": assessment_plan, "docstatus": 1})
+ results = frappe.get_all("Assessment Result", filters={"student": student,
+ "assessment_plan": assessment_plan, "docstatus": ("!=", 2)})
if results:
return frappe.get_doc("Assessment Result", results[0])
else:
return None
+
@frappe.whitelist()
def get_grade(grading_scale, percentage):
"""Returns Grade based on the Grading Scale and Score.
@@ -257,25 +280,63 @@
grade = ""
return grade
+
@frappe.whitelist()
-def mark_assessment_result(student, assessment_plan, scores):
- student_score = json.loads(scores)
- details = []
- for s in student_score.keys():
- details.append({
- "assessment_criteria": s,
- "score": flt(student_score[s])
+def mark_assessment_result(assessment_plan, scores):
+ student_score = json.loads(scores);
+ assessment_details = []
+ for criteria in student_score.get("assessment_details"):
+ assessment_details.append({
+ "assessment_criteria": criteria,
+ "score": flt(student_score["assessment_details"][criteria])
})
- assessment_result = frappe.new_doc("Assessment Result")
+ assessment_result = get_assessment_result_doc(student_score["student"], assessment_plan)
assessment_result.update({
- "student": student,
- "student_name": frappe.db.get_value("Student", student, "title"),
+ "student": student_score.get("student"),
"assessment_plan": assessment_plan,
- "details": details
+ "comment": student_score.get("comment"),
+ "total_score":student_score.get("total_score"),
+ "details": assessment_details
})
assessment_result.save()
- assessment_result.submit()
- return assessment_result
+ details = {}
+ for d in assessment_result.details:
+ details.update({d.assessment_criteria: d.grade})
+ assessment_result_dict = {
+ "name": assessment_result.name,
+ "student": assessment_result.student,
+ "total_score": assessment_result.total_score,
+ "grade": assessment_result.grade,
+ "details": details
+ }
+ return assessment_result_dict
+
+
+@frappe.whitelist()
+def submit_assessment_results(assessment_plan, student_group):
+ total_result = 0
+ student_list = get_student_group_students(student_group)
+ for i, student in enumerate(student_list):
+ doc = get_result(student.student, assessment_plan)
+ if doc and doc.docstatus==0:
+ total_result += 1
+ doc.submit()
+ return total_result
+
+
+def get_assessment_result_doc(student, assessment_plan):
+ assessment_result = frappe.get_all("Assessment Result", filters={"student": student,
+ "assessment_plan": assessment_plan, "docstatus": ("!=", 2)})
+ if assessment_result:
+ doc = frappe.get_doc("Assessment Result", assessment_result[0])
+ if doc.docstatus == 0:
+ return doc
+ elif doc.docstatus == 1:
+ frappe.msgprint("Result already Submitted")
+ return None
+ else:
+ return frappe.new_doc("Assessment Result")
+
@frappe.whitelist()
def update_email_group(doctype, name):
diff --git a/erpnext/schools/doctype/assessment_result/assessment_result.json b/erpnext/schools/doctype/assessment_result/assessment_result.json
index c6b3c44..13b927c 100644
--- a/erpnext/schools/doctype/assessment_result/assessment_result.json
+++ b/erpnext/schools/doctype/assessment_result/assessment_result.json
@@ -410,7 +410,7 @@
"collapsible": 0,
"columns": 0,
"fieldname": "comment",
- "fieldtype": "Long Text",
+ "fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -474,7 +474,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-06-30 08:21:46.875594",
+ "modified": "2017-08-31 15:39:24.813328",
"modified_by": "Administrator",
"module": "Schools",
"name": "Assessment Result",
diff --git a/erpnext/schools/doctype/assessment_result/assessment_result.py b/erpnext/schools/doctype/assessment_result/assessment_result.py
index c878ec3..3c036dd 100644
--- a/erpnext/schools/doctype/assessment_result/assessment_result.py
+++ b/erpnext/schools/doctype/assessment_result/assessment_result.py
@@ -9,13 +9,18 @@
from frappe.model.document import Document
from erpnext.schools.api import get_grade
from erpnext.schools.api import get_assessment_details
+from frappe.utils.csvutils import getlink
+
class AssessmentResult(Document):
def validate(self):
+ if self.student and not self.student_name:
+ self.student_name = frappe.db.get_value("Student", self.student, "title")
self.grading_scale = frappe.db.get_value("Assessment Plan", self.assessment_plan, "grading_scale")
self.validate_maximum_score()
self.validate_grade()
-
+ self.validate_duplicate()
+
def validate_maximum_score(self):
self.maximum_score = frappe.db.get_value("Assessment Plan", self.assessment_plan, "maximum_assessment_score")
assessment_details = get_assessment_details(self.assessment_plan)
@@ -34,3 +39,13 @@
d.grade = get_grade(self.grading_scale, (flt(d.score)/d.maximum_score)*100)
self.total_score += d.score
self.grade = get_grade(self.grading_scale, (self.total_score/self.maximum_score)*100)
+
+ def validate_duplicate(self):
+ assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]),
+ "student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)})
+ if assessment_result:
+ frappe.throw(_("Assessment Result record {0} already exists.".format(getlink("Assessment Result",assessment_result[0].name))))
+
+
+
+
diff --git a/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.js b/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.js
index a2eecef..dfa7b14 100644
--- a/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.js
+++ b/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.js
@@ -1,12 +1,13 @@
-
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-cur_frm.add_fetch("assessment_plan", "student_group", "student_group");
frappe.ui.form.on('Assessment Result Tool', {
+ setup: function(frm) {
+ frm.add_fetch("assessment_plan", "student_group", "student_group");
+ },
+
refresh: function(frm) {
- frm.trigger("assessment_plan");
if (frappe.route_options) {
frm.set_value("student_group", frappe.route_options.student_group);
frm.set_value("assessment_plan", frappe.route_options.assessment_plan);
@@ -14,98 +15,145 @@
}
frm.disable_save();
frm.page.clear_indicator();
+ frm.trigger("assessment_plan");
},
assessment_plan: function(frm) {
- if(!frm.doc.student_group) return;
- frappe.call({
- method: "erpnext.schools.api.get_assessment_students",
- args: {
- "assessment_plan": frm.doc.assessment_plan,
- "student_group": frm.doc.student_group
- },
- callback: function(r) {
- frm.events.render_table(frm, r.message);
- }
- });
+ frm.doc.show_submit = false;
+ if(frm.doc.assessment_plan) {
+ if (!frm.doc.student_group)
+ return
+ frappe.call({
+ method: "erpnext.schools.api.get_assessment_students",
+ args: {
+ "assessment_plan": frm.doc.assessment_plan,
+ "student_group": frm.doc.student_group
+ },
+ callback: function(r) {
+ frm.doc.students = r.message;
+ frm.events.render_table(frm);
+ for (let value of r.message) {
+ if (!value.docstatus) {
+ frm.doc.show_submit = true;
+ break;
+ }
+ }
+ frm.events.submit_result(frm);
+ }
+ });
+ }
},
- render_table: function(frm, students) {
+ render_table: function(frm) {
$(frm.fields_dict.result_html.wrapper).empty();
- var assessment_plan = frm.doc.assessment_plan;
- var student_scores = {};
- students.forEach(function(stu) {
- student_scores[stu.student] = {}
- });
-
+ let assessment_plan = frm.doc.assessment_plan;
frappe.call({
method: "erpnext.schools.api.get_assessment_details",
args: {
assessment_plan: assessment_plan
},
callback: function(r) {
- var criteria_list = r.message;
- var max_total_score = 0;
- criteria_list.forEach(function(c) {
- max_total_score += c.maximum_score
- });
- var result_table = $(frappe.render_template('assessment_result_tool', {
- frm: frm,
- students: students,
- criteria: criteria_list,
- max_total_score: max_total_score
- }));
- result_table.appendTo(frm.fields_dict.result_html.wrapper)
-
- result_table.on('change', 'input', function(e) {
- var $input = $(e.target);
- var max_score = $input.data().maxScore;
- var student = $input.data().student;
- var criteria = $input.data().criteria;
- var value = $input.val();
- if(value < 0) {
- $input.val(0);
- value = 0;
- }
- if(value > max_score) {
- $input.val(max_score);
- value = max_score;
- }
- student_scores[student][criteria] = value;
- if(Object.keys(student_scores[student]).length == criteria_list.length) {
- console.log("ok");
- frappe.call(({
- method: "erpnext.schools.api.mark_assessment_result",
- args: {
- "student": student,
- "assessment_plan": assessment_plan,
- "scores": student_scores[student]
- },
- callback: function(r) {
- var doc = r.message;
- var student = doc.student;
- result_table.find(`[data-student=${student}].total-score`)
- .html(doc.total_score + ' ('+ doc.grade + ')');
- var details = doc.details;
- result_table.find(`tr[data-student=${student}]`).addClass('text-muted');
- result_table.find(`input[data-student=${student}]`).each(function(el, input) {
- var $input = $(input);
- var criteria = $input.data().criteria;
- var value = $input.val();
- var grade = details.find(function(d) {
- return d.assessment_criteria === criteria;
- }).grade;
- $input.val(`${value} (${grade})`);
- $input.attr('disabled', true);
- });
-
- }
- }))
- }
- });
-
+ frm.events.get_marks(frm, r.message);
}
});
},
+ get_marks: function(frm, criteria_list) {
+ let max_total_score = 0;
+ criteria_list.forEach(function(c) {
+ max_total_score += c.maximum_score
+ });
+ var result_table = $(frappe.render_template('assessment_result_tool', {
+ frm: frm,
+ students: frm.doc.students,
+ criteria: criteria_list,
+ max_total_score: max_total_score
+ }));
+ result_table.appendTo(frm.fields_dict.result_html.wrapper);
+
+ result_table.on('change', 'input', function(e) {
+ let $input = $(e.target);
+ let student = $input.data().student;
+ let max_score = $input.data().maxScore;
+ let value = $input.val();
+ if(value < 0) {
+ $input.val(0);
+ } else if(value > max_score) {
+ $input.val(max_score);
+ }
+ let total_score = 0;
+ let student_scores = {};
+ student_scores["assessment_details"] = {}
+ result_table.find(`input[data-student=${student}].student-result-data`)
+ .each(function(el, input) {
+ let $input = $(input);
+ let criteria = $input.data().criteria;
+ let value = parseFloat($input.val());
+ if (value) {
+ student_scores["assessment_details"][criteria] = value;
+ }
+ total_score += value;
+ });
+ if(!Number.isNaN(total_score)) {
+ result_table.find(`span[data-student=${student}].total-score`).html(total_score);
+ }
+ if (Object.keys(student_scores["assessment_details"]).length === criteria_list.length) {
+ student_scores["student"] = student;
+ student_scores["total_score"] = total_score;
+ result_table.find(`[data-student=${student}].result-comment`)
+ .each(function(el, input){
+ student_scores["comment"] = $(input).val();
+ });
+ frappe.call({
+ method: "erpnext.schools.api.mark_assessment_result",
+ args: {
+ "assessment_plan": frm.doc.assessment_plan,
+ "scores": student_scores
+ },
+ callback: function(r) {
+ let assessment_result = r.message;
+ if (!frm.doc.show_submit) {
+ frm.doc.show_submit = true;
+ frm.events.submit_result;
+ }
+ for (var criteria of Object.keys(assessment_result.details)) {
+ result_table.find(`[data-criteria=${criteria}][data-student=${assessment_result
+ .student}].student-result-grade`).each(function(e1, input) {
+ $(input).html(assessment_result.details[criteria]);
+ });
+ }
+ result_table.find(`span[data-student=${assessment_result.student}].total-score-grade`).html(assessment_result.grade);
+ let link_span = result_table.find(`span[data-student=${assessment_result.student}].total-result-link`);
+ $(link_span).css("display", "block");
+ $(link_span).find("a").attr("href", "#Form/Assessment Result/"+assessment_result.name);
+ }
+ });
+ }
+ });
+ },
+
+ submit_result: function(frm) {
+ if (frm.doc.show_submit) {
+ frm.page.set_primary_action(__("Submit"), function() {
+ frappe.call({
+ method: "erpnext.schools.api.submit_assessment_results",
+ args: {
+ "assessment_plan": frm.doc.assessment_plan,
+ "student_group": frm.doc.student_group
+ },
+ callback: function(r) {
+ if (r.message) {
+ frappe.msgprint(__("{0} Result submittted", [r.message]));
+ } else {
+ frappe.msgprint(__("No Result to submit"));
+ }
+ frm.events.assessment_plan(frm);
+ }
+ });
+ });
+ }
+ else {
+ frm.page.clear_primary_action();
+ }
+ }
});
diff --git a/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.py b/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.py
index a0d286c..649f420 100644
--- a/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.py
+++ b/erpnext/schools/doctype/assessment_result_tool/assessment_result_tool.py
@@ -7,4 +7,4 @@
from frappe.model.document import Document
class AssessmentResultTool(Document):
- pass
+ pass
\ No newline at end of file