Merge pull request #2081 from pateljannat/batch-dashboard-update
Batch dashboard update
This commit is contained in:
@@ -50,7 +50,7 @@ frappe.ui.form.on("LMS Batch", {
|
||||
refresh: (frm) => {
|
||||
const lmsPath = frappe.boot.lms_path || "lms";
|
||||
frm.add_web_link(
|
||||
`/${lmsPath}/batches/details/${frm.doc.name}`,
|
||||
`/${lmsPath}/batches/${frm.doc.name}`,
|
||||
"See on website"
|
||||
);
|
||||
},
|
||||
|
||||
@@ -9,41 +9,43 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_earo",
|
||||
"published",
|
||||
"title",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"column_break_4",
|
||||
"allow_self_enrollment",
|
||||
"start_time",
|
||||
"end_time",
|
||||
"timezone",
|
||||
"section_break_wuxt",
|
||||
"seat_count",
|
||||
"column_break_uamg",
|
||||
"category",
|
||||
"section_break_cssv",
|
||||
"published",
|
||||
"evaluation",
|
||||
"evaluation_end_date",
|
||||
"column_break_wfkz",
|
||||
"allow_self_enrollment",
|
||||
"column_break_vnrp",
|
||||
"certification",
|
||||
"section_break_6",
|
||||
"description",
|
||||
"column_break_hlqw",
|
||||
"instructors",
|
||||
"zoom_account",
|
||||
"column_break_hlqw",
|
||||
"batch_details",
|
||||
"section_break_rgfj",
|
||||
"medium",
|
||||
"category",
|
||||
"confirmation_email_template",
|
||||
"column_break_flwy",
|
||||
"seat_count",
|
||||
"evaluation_end_date",
|
||||
"zoom_account",
|
||||
"notification_sent",
|
||||
"section_break_jedp",
|
||||
"video_link",
|
||||
"column_break_kpct",
|
||||
"meta_image",
|
||||
"section_break_khcn",
|
||||
"batch_details",
|
||||
"batch_details_raw",
|
||||
"section_break_jgji",
|
||||
"courses",
|
||||
"section_break_khcn",
|
||||
"batch_details_raw",
|
||||
"assessment_tab",
|
||||
"assessment",
|
||||
"schedule_tab",
|
||||
@@ -297,6 +299,7 @@
|
||||
"label": "Allow accessing future dates"
|
||||
},
|
||||
{
|
||||
"depends_on": "evaluation",
|
||||
"fieldname": "evaluation_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Evaluation End Date"
|
||||
@@ -341,10 +344,6 @@
|
||||
"fieldname": "column_break_wfkz",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vnrp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "certification",
|
||||
@@ -358,7 +357,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_cssv",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Certification"
|
||||
},
|
||||
{
|
||||
"fieldname": "zoom_account",
|
||||
@@ -385,6 +385,20 @@
|
||||
{
|
||||
"fieldname": "column_break_kpct",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "evaluation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Evaluation"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_wuxt",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_uamg",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -407,7 +421,7 @@
|
||||
"link_fieldname": "payment_for_document"
|
||||
}
|
||||
],
|
||||
"modified": "2026-01-13 18:50:27.420712",
|
||||
"modified": "2026-02-13 14:23:51.913875",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch",
|
||||
|
||||
@@ -165,7 +165,7 @@ def send_email_notification_for_published_batch(batch):
|
||||
"medium": batch.medium,
|
||||
"timezone": batch.timezone,
|
||||
"instructors": instructors,
|
||||
"batch_url": frappe.utils.get_url(get_lms_route(f"batches/details/{batch.name}")),
|
||||
"batch_url": frappe.utils.get_url(get_lms_route(f"batches/{batch.name}")),
|
||||
}
|
||||
|
||||
frappe.sendmail(
|
||||
@@ -194,7 +194,7 @@ def send_system_notification_for_published_batch(batch):
|
||||
"document_name": batch.name,
|
||||
"from_user": instructors[0] if instructors else None,
|
||||
"type": "Alert",
|
||||
"link": get_lms_route(f"batches/details/{batch.name}"),
|
||||
"link": get_lms_route(f"batches/{batch.name}"),
|
||||
}
|
||||
)
|
||||
make_notification_logs(notification, students)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"member",
|
||||
"member_name",
|
||||
"member_username",
|
||||
"member_image",
|
||||
"column_break_sjzm",
|
||||
"batch",
|
||||
"payment",
|
||||
@@ -70,11 +71,17 @@
|
||||
"label": "Batch",
|
||||
"options": "LMS Batch",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "member.user_image",
|
||||
"fieldname": "member_image",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Member Image"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-03 10:51:28.475356",
|
||||
"modified": "2026-02-10 16:07:28.315982",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch Enrollment",
|
||||
|
||||
+59
-77
@@ -1099,7 +1099,7 @@ def get_batch_details(batch: str):
|
||||
is_student_enrolled = frappe.session.user in batch_students
|
||||
|
||||
if not (is_batch_published or is_batch_admin or is_student_enrolled):
|
||||
return
|
||||
return {}
|
||||
|
||||
batch_details = frappe.db.get_value(
|
||||
"LMS Batch",
|
||||
@@ -1123,6 +1123,7 @@ def get_batch_details(batch: str):
|
||||
"evaluation_end_date",
|
||||
"allow_self_enrollment",
|
||||
"certification",
|
||||
"evaluation",
|
||||
"timezone",
|
||||
"category",
|
||||
"zoom_account",
|
||||
@@ -1143,6 +1144,10 @@ def get_batch_details(batch: str):
|
||||
batch_details.courses = frappe.get_all(
|
||||
"Batch Course", filters={"parent": batch}, fields=["course", "title", "evaluator"]
|
||||
)
|
||||
batch_details.assessments = frappe.get_all(
|
||||
"LMS Assessment", {"parent": batch}, ["assessment_name", "assessment_type"]
|
||||
)
|
||||
|
||||
if can_modify_batch(batch):
|
||||
batch_details.students = batch_students
|
||||
elif is_student_enrolled:
|
||||
@@ -1342,43 +1347,13 @@ def get_exercise_details(assessment: dict, member: str) -> dict:
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_batch_assessment_count(batch: str) -> int:
|
||||
frappe.only_for(["Moderator", "Batch Evaluator"])
|
||||
if not frappe.db.exists("LMS Batch", batch):
|
||||
frappe.throw(_("The specified batch does not exist."))
|
||||
return frappe.db.count("LMS Assessment", {"parent": batch})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_batch_students(
|
||||
filters: dict, offset: int = 0, limit_start: int = 0, limit_page_length: int = None, limit: int = None
|
||||
):
|
||||
# limit_start and limit_page_length are used for backward compatibility
|
||||
start = limit_start or offset
|
||||
page_length = limit_page_length or limit
|
||||
batch = filters.get("batch")
|
||||
if not batch:
|
||||
return []
|
||||
|
||||
def get_batch_student_progress(member: str, batch: str) -> dict:
|
||||
if not can_modify_batch(batch):
|
||||
frappe.throw(_("You are not authorized to view the students of this batch."))
|
||||
|
||||
students = []
|
||||
students_list = frappe.get_all(
|
||||
"LMS Batch Enrollment",
|
||||
filters={"batch": batch},
|
||||
fields=["member", "name"],
|
||||
offset=start,
|
||||
limit=page_length,
|
||||
order_by="creation desc",
|
||||
)
|
||||
|
||||
for student in students_list:
|
||||
details = get_batch_student_details(student)
|
||||
calculate_student_progress(batch, details)
|
||||
students.append(details)
|
||||
|
||||
return students
|
||||
details = get_batch_student_details(member)
|
||||
calculate_student_progress(batch, details)
|
||||
return details
|
||||
|
||||
|
||||
def get_course_completion_stats(batch: str) -> list:
|
||||
@@ -1472,16 +1447,14 @@ def get_batch_chart_data(batch: str) -> list:
|
||||
return get_course_completion_stats(batch) + get_assignment_pass_stats(batch) + get_quiz_pass_stats(batch)
|
||||
|
||||
|
||||
def get_batch_student_details(student: dict) -> dict:
|
||||
def get_batch_student_details(student: str) -> dict:
|
||||
details = frappe.db.get_value(
|
||||
"User",
|
||||
student.member,
|
||||
["full_name", "email", "username", "last_active", "user_image"],
|
||||
student,
|
||||
["full_name", "email", "username", "last_active", "user_image", "name"],
|
||||
as_dict=True,
|
||||
)
|
||||
details.last_active = format_datetime(details.last_active, "dd MMM YY")
|
||||
details.name = student.name
|
||||
details.assessments = frappe._dict()
|
||||
return details
|
||||
|
||||
|
||||
@@ -1511,8 +1484,7 @@ def calculate_student_progress(batch: str, details: dict):
|
||||
|
||||
def calculate_course_progress(batch_courses: list, details: dict):
|
||||
course_progress = []
|
||||
details.courses = frappe._dict()
|
||||
|
||||
details.courses = []
|
||||
for course in batch_courses:
|
||||
progress = (
|
||||
frappe.db.get_value(
|
||||
@@ -1520,7 +1492,7 @@ def calculate_course_progress(batch_courses: list, details: dict):
|
||||
)
|
||||
or 0
|
||||
)
|
||||
details.courses[course.title] = progress
|
||||
details.courses.append({"course": course.course, "title": course.title, "progress": progress})
|
||||
course_progress.append(progress)
|
||||
|
||||
details.average_course_progress = (
|
||||
@@ -1530,14 +1502,15 @@ def calculate_course_progress(batch_courses: list, details: dict):
|
||||
|
||||
def calculate_assessment_progress(assessments: list, details: dict):
|
||||
assessments_completed = 0
|
||||
details.assessments = frappe._dict()
|
||||
details.assessments = []
|
||||
|
||||
for assessment in assessments:
|
||||
title = frappe.db.get_value(assessment.assessment_type, assessment.assessment_name, "title")
|
||||
assessment_info = has_submitted_assessment(
|
||||
assessment.assessment_name, assessment.assessment_type, details.email
|
||||
)
|
||||
details.assessments[title] = assessment_info
|
||||
assessment_info.title = title
|
||||
details.assessments.append(assessment_info)
|
||||
|
||||
if assessment_info.result == "Pass":
|
||||
assessments_completed += 1
|
||||
@@ -1551,6 +1524,24 @@ def has_submitted_assessment(assessment: str, assessment_type: str, member: str
|
||||
if not member:
|
||||
member = frappe.session.user
|
||||
|
||||
doctype, docfield, fields, not_attempted = get_assessment_meta(assessment_type)
|
||||
filters = {}
|
||||
filters[docfield] = assessment
|
||||
filters["member"] = member
|
||||
|
||||
attempt = frappe.db.exists(doctype, filters)
|
||||
if attempt:
|
||||
return get_assessment_attempt_details(doctype, filters, fields, assessment_type, assessment)
|
||||
else:
|
||||
return frappe._dict(
|
||||
{
|
||||
"status": not_attempted,
|
||||
"result": "Failed",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_assessment_meta(assessment_type: str):
|
||||
if assessment_type == "LMS Assignment":
|
||||
doctype = "LMS Assignment Submission"
|
||||
docfield = "assignment"
|
||||
@@ -1567,39 +1558,30 @@ def has_submitted_assessment(assessment: str, assessment_type: str, member: str
|
||||
fields = ["status"]
|
||||
not_attempted = "Not Attempted"
|
||||
|
||||
filters = {}
|
||||
filters[docfield] = assessment
|
||||
filters["member"] = member
|
||||
return doctype, docfield, fields, not_attempted
|
||||
|
||||
attempt = frappe.db.exists(doctype, filters)
|
||||
if attempt:
|
||||
fields.append("name")
|
||||
attempt_details = frappe.db.get_value(doctype, filters, fields, as_dict=1)
|
||||
if assessment_type == "LMS Quiz":
|
||||
result = "Failed"
|
||||
passing_percentage = frappe.db.get_value("LMS Quiz", assessment, "passing_percentage")
|
||||
if attempt_details.percentage >= passing_percentage:
|
||||
result = "Pass"
|
||||
else:
|
||||
result = attempt_details.status
|
||||
return frappe._dict(
|
||||
{
|
||||
"status": attempt_details.percentage
|
||||
if assessment_type == "LMS Quiz"
|
||||
else attempt_details.status,
|
||||
"result": result,
|
||||
"assessment": assessment,
|
||||
"type": assessment_type,
|
||||
"submission": attempt_details.name,
|
||||
}
|
||||
)
|
||||
|
||||
def get_assessment_attempt_details(
|
||||
doctype: str, filters: dict, fields: list, assessment_type: str, assessment: str
|
||||
):
|
||||
fields.append("name")
|
||||
attempt_details = frappe.db.get_value(doctype, filters, fields, as_dict=1)
|
||||
if assessment_type == "LMS Quiz":
|
||||
result = "Failed"
|
||||
passing_percentage = frappe.db.get_value("LMS Quiz", assessment, "passing_percentage")
|
||||
if attempt_details.percentage >= passing_percentage:
|
||||
result = "Pass"
|
||||
else:
|
||||
return frappe._dict(
|
||||
{
|
||||
"status": not_attempted,
|
||||
"result": "Failed",
|
||||
}
|
||||
)
|
||||
result = attempt_details.status
|
||||
return frappe._dict(
|
||||
{
|
||||
"status": attempt_details.percentage if assessment_type == "LMS Quiz" else attempt_details.status,
|
||||
"result": result,
|
||||
"assessment": assessment,
|
||||
"type": assessment_type,
|
||||
"submission": attempt_details.name,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def can_access_topic(doctype: str, docname: str) -> bool:
|
||||
@@ -1665,7 +1647,7 @@ def create_discussion_topic(doctype: str, docname: str) -> str:
|
||||
@frappe.whitelist()
|
||||
def get_discussion_replies(topic: str):
|
||||
topic_details = frappe.db.get_value(
|
||||
"Discussion Topic", topic, ["reference_doctype", "reference_docname"], as_dict=1
|
||||
"Discussion Topic", topic, ["reference_doctype", "reference_docname"], as_dict=True
|
||||
)
|
||||
if not can_access_topic(topic_details.reference_doctype, topic_details.reference_docname):
|
||||
frappe.throw(_("You are not authorized to view the discussion replies for this topic."))
|
||||
|
||||
Reference in New Issue
Block a user