diff --git a/frontend/src/components/AdminBatchDashboard.vue b/frontend/src/components/AdminBatchDashboard.vue index 934ab995..947180c5 100644 --- a/frontend/src/components/AdminBatchDashboard.vue +++ b/frontend/src/components/AdminBatchDashboard.vue @@ -8,7 +8,7 @@
@@ -37,7 +37,7 @@ v-if="showProgressChart" class="border" :config="{ - data: chartData || [], + data: filteredChartData, title: __('Batch Summary'), subtitle: __('Progress of students in courses and assessments'), xAxis: { @@ -64,96 +64,50 @@ diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 998e79c0..0acb2565 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -24,6 +24,8 @@ from frappe.utils import ( pretty_date, rounded, ) +from pypika import Case +from pypika import functions as fn from lms.lms.md import find_macros @@ -1381,6 +1383,116 @@ def get_batch_students(filters, offset=0, limit_start=0, limit_page_length=None, return students +@frappe.whitelist() +def get_batch_student_count(batch): + if not frappe.db.exists("LMS Batch", batch): + frappe.throw(_("The specified batch does not exist.")) + return frappe.db.count("LMS Batch Enrollment", filters={"batch": batch}) + + +@frappe.whitelist() +def get_batch_certificate_count(batch): + if not frappe.db.exists("LMS Batch", batch): + frappe.throw(_("The specified batch does not exist.")) + return frappe.db.count("LMS Certificate", filters={"batch_name": batch}) + + +@frappe.whitelist() +def get_batch_assessment_count(batch): + if not frappe.db.exists("LMS Batch", batch): + frappe.throw(_("The specified batch does not exist.")) + return frappe.db.count("LMS Assessment", filters={"parent": batch}) + + +def get_course_completion_stats(batch): + """Get completion counts per course in batch""" + BatchCourse = frappe.qb.DocType("Batch Course") + BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment") + Enrollment = frappe.qb.DocType("LMS Enrollment") + + rows = ( + frappe.qb.from_(BatchCourse) + .left_join(BatchEnrollment) + .on(BatchEnrollment.batch == BatchCourse.parent) + .left_join(Enrollment) + .on((Enrollment.course == BatchCourse.course) & (Enrollment.member == BatchEnrollment.member)) + .where(BatchCourse.parent == batch) + .groupby(BatchCourse.course, BatchCourse.title) + .select( + BatchCourse.title, + fn.Count(Case().when(Enrollment.progress == 100, Enrollment.member)).distinct().as_("completed"), + ) + ).run(as_dict=True) + + return [{"task": row.title, "value": row.completed or 0} for row in rows] + + +def get_assignment_pass_stats(batch): + """Get pass counts per assignment in batch""" + Assessment = frappe.qb.DocType("LMS Assessment") + Assignment = frappe.qb.DocType("LMS Assignment") + BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment") + Submission = frappe.qb.DocType("LMS Assignment Submission") + + rows = ( + frappe.qb.from_(Assessment) + .join(Assignment) + .on(Assignment.name == Assessment.assessment_name) + .left_join(BatchEnrollment) + .on(BatchEnrollment.batch == Assessment.parent) + .left_join(Submission) + .on( + (Submission.assignment == Assessment.assessment_name) + & (Submission.member == BatchEnrollment.member) + ) + .where((Assessment.parent == batch) & (Assessment.assessment_type == "LMS Assignment")) + .groupby(Assessment.assessment_name, Assignment.title) + .select( + Assignment.title, + fn.Count(Case().when(Submission.status == "Pass", Submission.member)).distinct().as_("passed"), + ) + ).run(as_dict=True) + + return [{"task": row.title, "value": row.passed or 0} for row in rows] + + +def get_quiz_pass_stats(batch): + """Get pass counts per quiz in batch""" + Assessment = frappe.qb.DocType("LMS Assessment") + Quiz = frappe.qb.DocType("LMS Quiz") + BatchEnrollment = frappe.qb.DocType("LMS Batch Enrollment") + Submission = frappe.qb.DocType("LMS Quiz Submission") + + rows = ( + frappe.qb.from_(Assessment) + .join(Quiz) + .on(Quiz.name == Assessment.assessment_name) + .left_join(BatchEnrollment) + .on(BatchEnrollment.batch == Assessment.parent) + .left_join(Submission) + .on((Submission.quiz == Assessment.assessment_name) & (Submission.member == BatchEnrollment.member)) + .where((Assessment.parent == batch) & (Assessment.assessment_type == "LMS Quiz")) + .groupby(Assessment.assessment_name, Quiz.title) + .select( + Quiz.title, + fn.Count(Case().when(Submission.percentage >= Submission.passing_percentage, Submission.member)) + .distinct() + .as_("passed"), + ) + ).run(as_dict=True) + + return [{"task": row.title, "value": row.passed or 0} for row in rows] + + +@frappe.whitelist() +def get_batch_chart_data(batch): + """Get completion counts per course and assessment""" + if not frappe.db.exists("LMS Batch", batch): + frappe.throw(_("The specified batch does not exist.")) + + return get_course_completion_stats(batch) + get_assignment_pass_stats(batch) + get_quiz_pass_stats(batch) + + def get_batch_student_details(student): details = frappe.db.get_value( "User", @@ -1423,8 +1535,11 @@ def calculate_course_progress(batch_courses, details): details.courses = frappe._dict() for course in batch_courses: - progress = frappe.db.get_value( - "LMS Enrollment", {"course": course.course, "member": details.email}, "progress" + progress = ( + frappe.db.get_value( + "LMS Enrollment", {"course": course.course, "member": details.email}, "progress" + ) + or 0 ) details.courses[course.title] = progress course_progress.append(progress)