Merge pull request #1300 from pateljannat/issues-73

fix: misc batch enrollment issues
This commit is contained in:
Jannat Patel
2025-02-11 15:02:14 +05:30
committed by GitHub
25 changed files with 220 additions and 258 deletions

View File

@@ -333,7 +333,7 @@ const getChartData = () => {
})
Object.keys(student.assessments).forEach((assessment) => {
if (student.assessments[assessment] === 100) {
if (student.assessments[assessment].result === 'Passed') {
categories[assessment].value += 1
}
})

View File

@@ -316,6 +316,9 @@ const quiz = createResource({
},
cache: ['quiz', props.quizName],
auto: true,
transform(data) {
data.duration = parseInt(data.duration)
},
onSuccess(data) {
populateQuestions()
setupTimer()

View File

@@ -44,7 +44,7 @@ const certificates = createListResource({
filters: {
member: props.profile.data?.name,
},
fields: ['name', 'course_title', 'batch_title', 'issue_date'],
fields: ['name', 'course_title', 'batch_title', 'issue_date', 'template'],
cache: ['certificates', props.profile.data?.name],
})

View File

@@ -113,7 +113,10 @@ scheduler_events = {
"lms.lms.doctype.lms_certificate_request.lms_certificate_request.schedule_evals",
"lms.lms.api.update_course_statistics",
],
"daily": ["lms.job.doctype.job_opportunity.job_opportunity.update_job_openings"],
"daily": [
"lms.job.doctype.job_opportunity.job_opportunity.update_job_openings",
"lms.lms.doctype.lms_payment.lms_payment.send_payment_reminder",
],
}
fixtures = ["Custom Field", "Function", "Industry", "LMS Category"]

View File

@@ -1,7 +0,0 @@
// Copyright (c) 2022, Frappe and contributors
// For license information, please see license.txt
frappe.ui.form.on("Batch Student", {
// refresh: function(frm) {
// }
});

View File

@@ -1,83 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-11-09 16:20:44.602545",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"student_details_section",
"student",
"student_name",
"username",
"column_break_oduu",
"payment",
"source",
"confirmation_email_sent"
],
"fields": [
{
"fieldname": "student",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Student",
"options": "User",
"reqd": 1
},
{
"fetch_from": "student.full_name",
"fieldname": "student_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Student Name",
"read_only": 1
},
{
"fetch_from": "student.username",
"fieldname": "username",
"fieldtype": "Data",
"label": "Username",
"read_only": 1
},
{
"fieldname": "student_details_section",
"fieldtype": "Section Break",
"label": "Student Details"
},
{
"fieldname": "column_break_oduu",
"fieldtype": "Column Break"
},
{
"fieldname": "payment",
"fieldtype": "Link",
"label": "Payment",
"options": "LMS Payment"
},
{
"default": "0",
"fieldname": "confirmation_email_sent",
"fieldtype": "Check",
"label": "Confirmation Email Sent"
},
{
"fieldname": "source",
"fieldtype": "Link",
"label": "Source",
"options": "LMS Source"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-10-26 16:52:04.266694",
"modified_by": "Administrator",
"module": "LMS",
"name": "Batch Student",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2022, Frappe and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
class BatchStudent(Document):
pass

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2022, Frappe and Contributors
# See license.txt
# import frappe
from frappe.tests import UnitTestCase
class TestBatchStudent(UnitTestCase):
pass

View File

@@ -41,16 +41,6 @@ class LMSBatch(Document):
if self.end_date < self.start_date:
frappe.throw(_("Batch end date cannot be before the batch start date"))
def validate_duplicate_students(self):
students = [row.student for row in self.students]
duplicates = {student for student in students if students.count(student) > 1}
if len(duplicates):
frappe.throw(
_("Student {0} has already been added to this batch.").format(
frappe.bold(next(iter(duplicates)))
)
)
def validate_duplicate_courses(self):
courses = [row.course for row in self.courses]
duplicates = {course for course in courses if courses.count(course) > 1}
@@ -88,9 +78,7 @@ class LMSBatch(Document):
frappe.throw(_("Evaluation end date cannot be less than the batch end date."))
def validate_membership(self):
members = frappe.get_all(
"LMS Batch Enrollment", filters={"batch": self.name}, pluck=["member"]
)
members = frappe.get_all("LMS Batch Enrollment", {"batch": self.name}, pluck="member")
for course in self.courses:
for member in members:
if not frappe.db.exists(
@@ -102,7 +90,8 @@ class LMSBatch(Document):
enrollment.save()
def validate_seats_left(self):
if cint(self.seat_count) < len(self.students):
students = frappe.db.count("LMS Batch Enrollment", {"batch": self.name})
if cint(self.seat_count) < students:
frappe.throw(_("There are no seats available in this batch."))
def validate_timetable(self):

View File

@@ -1,8 +1,20 @@
// Copyright (c) 2025, Frappe and contributors
// For license information, please see license.txt
// frappe.ui.form.on("LMS Batch Enrollment", {
// refresh(frm) {
// },
// });
frappe.ui.form.on("LMS Batch Enrollment", {
refresh(frm) {
if (!frm.doc.confirmation_email_sent) {
frm.add_custom_button(__("Send Confirmation Email"), function () {
frappe.call({
method: "lms.lms.doctype.lms_batch_enrollment.lms_batch_enrollment.send_confirmation_email",
args: {
doc: frm.doc,
},
callback: function (r) {
frm.refresh();
},
});
});
}
},
});

View File

@@ -2,6 +2,7 @@
# For license information, please see license.txt
import frappe
import json
from frappe import _
from frappe.model.document import Document
from frappe.email.doctype.email_template.email_template import get_email_template
@@ -9,7 +10,7 @@ from frappe.email.doctype.email_template.email_template import get_email_templat
class LMSBatchEnrollment(Document):
def after_insert(self):
self.send_confirmation_email()
send_confirmation_email(self)
self.add_member_to_live_class()
def validate(self):
@@ -37,53 +38,6 @@ class LMSBatchEnrollment(Document):
enrollment.member = self.member
enrollment.save()
def send_confirmation_email(self):
if not self.confirmation_email_sent:
outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)
if not self.confirmation_email_sent and (
outgoing_email_account or frappe.conf.get("mail_login")
):
self.send_mail()
self.db_set("confirmation_email_sent", 1)
def send_mail(self):
subject = _("Enrollment Confirmation for the Next Training Batch")
template = "batch_confirmation"
custom_template = frappe.db.get_single_value(
"LMS Settings", "batch_confirmation_template"
)
batch = frappe.db.get_value(
"LMS Batch",
self.batch,
["name", "title", "start_date", "start_time", "medium"],
as_dict=1,
)
args = {
"title": batch.title,
"student_name": self.member_name,
"start_time": batch.start_time,
"start_date": batch.start_date,
"medium": batch.medium,
"name": batch.name,
}
if custom_template:
email_template = get_email_template(custom_template, args)
subject = email_template.get("subject")
content = email_template.get("message")
frappe.sendmail(
recipients=self.member,
subject=subject,
template=template if not custom_template else None,
content=content if custom_template else None,
args=args,
header=[subject, "green"],
retry=3,
)
def add_member_to_live_class(self):
live_classes = frappe.get_all(
"LMS Live Class", {"batch_name": self.batch}, ["name", "event"]
@@ -102,3 +56,56 @@ class LMSBatchEnrollment(Document):
"parentfield": "event_participants",
}
).save()
@frappe.whitelist()
def send_confirmation_email(doc):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
if not doc.confirmation_email_sent:
outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)
if not doc.confirmation_email_sent and (
outgoing_email_account or frappe.conf.get("mail_login")
):
doc.send_mail()
doc.db_set("confirmation_email_sent", 1)
def send_mail(doc):
subject = _("Enrollment Confirmation for the Next Training Batch")
template = "batch_confirmation"
custom_template = frappe.db.get_single_value(
"LMS Settings", "batch_confirmation_template"
)
batch = frappe.db.get_value(
"LMS Batch",
doc.batch,
["name", "title", "start_date", "start_time", "medium"],
as_dict=1,
)
args = {
"title": batch.title,
"student_name": doc.member_name,
"start_time": batch.start_time,
"start_date": batch.start_date,
"medium": batch.medium,
"name": batch.name,
}
if custom_template:
email_template = get_email_template(custom_template, args)
subject = email_template.get("subject")
content = email_template.get("message")
frappe.sendmail(
recipients=doc.member,
subject=subject,
template=template if not custom_template else None,
content=content if custom_template else None,
args=args,
header=[subject, "green"],
retry=3,
)

View File

@@ -1,19 +1,18 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:PAY-{#####}",
"creation": "2023-08-24 17:46:52.065763",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"payment_for_document_type",
"member",
"billing_name",
"source",
"column_break_rqkd",
"payment_for_document_type",
"payment_for_document",
"billing_name",
"payment_received",
"payment_details_section",
"currency",
@@ -141,11 +140,10 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-10-31 15:33:39.420366",
"modified": "2025-02-11 14:48:27.801895",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Payment",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{

View File

@@ -1,9 +1,78 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe import _
from frappe.utils import add_days, nowdate
from frappe.email.doctype.email_template.email_template import get_email_template
from frappe.model.document import Document
class LMSPayment(Document):
pass
def send_payment_reminder():
outgoing_email_account = frappe.get_cached_value(
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
)
if not (outgoing_email_account or frappe.conf.get("mail_login")):
return
incomplete_payments = frappe.get_all(
"LMS Payment",
{"payment_received": 0, "creation": [">", add_days(nowdate(), -1)]},
fields=[
"name",
"member",
"payment_for_document",
"payment_for_document_type",
"billing_name",
],
)
for payment in incomplete_payments:
send_mail(payment)
def send_mail(payment):
subject = _("Complete Your Enrollment - Don't miss out!")
template = "payment_reminder"
custom_template = frappe.db.get_single_value(
"LMS Settings", "payment_reminder_template"
)
args = {
"billing_name": payment.billing_name,
"type": payment.payment_for_document_type.split(" ")[-1].lower(),
"title": frappe.db.get_value(
payment.payment_for_document_type, payment.payment_for_document, "title"
),
"link": f"/lms/billing/{ payment.payment_for_document_type.split(' ')[-1].lower() }/{ payment.payment_for_document }",
}
if custom_template:
email_template = get_email_template(custom_template, args)
subject = email_template.get("subject")
content = email_template.get("message")
instructors = frappe.get_all(
"Course Instructor",
{
"parenttype": payment.payment_for_document_type,
"parent": payment.payment_for_document,
},
pluck="instructor",
)
frappe.sendmail(
recipients=payment.member,
cc=instructors,
subject=subject,
template=template if not custom_template else None,
content=content if custom_template else None,
args=args,
header=[subject, "green"],
retry=3,
)

View File

@@ -58,7 +58,8 @@
"certification_template",
"batch_confirmation_template",
"column_break_uwsp",
"assignment_submission_template"
"assignment_submission_template",
"payment_reminder_template"
],
"fields": [
{
@@ -358,12 +359,18 @@
"fieldname": "allow_guest_access",
"fieldtype": "Check",
"label": "Allow Guest Access"
},
{
"fieldname": "payment_reminder_template",
"fieldtype": "Link",
"label": "Payment Reminder Template",
"options": "Email Template"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-02-06 11:42:29.803207",
"modified": "2025-02-11 11:29:43.412897",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Settings",

View File

@@ -1,11 +0,0 @@
<div>
<p>Hi {{ doc.member_name }},</p>
<p>We noticed that you started enrolling in the {{ doc.payment_for_document_type.split(" ")[-1] }} {{ frappe.db.get_value(doc.payment_for_document_type, doc.payment_for_document, "title") }} but didnt complete your payment.</p>
<p>We have a limited number of seats, and they won't be available for long!</p>
<p>Dont miss this opportunity to enhance your skills. Click below to complete your enrollment:</p>
<p>
<a href="/lms/billing/{{ doc.payment_for_document_type.split(' ')[-1].lower() }}/{{ doc.payment_for_document }}">👉 Complete Your Enrollment</a>
</p>
<p>If you have any questions or need assistance, feel free to reach out to our support team.</p>
<p>Looking forward to seeing you enrolled!</p>
</div>

View File

@@ -1,35 +0,0 @@
{
"attach_print": 0,
"channel": "Email",
"condition": "doc.payment_received == 0",
"creation": "2025-02-03 15:52:32.508093",
"date_changed": "creation",
"days_in_advance": 1,
"docstatus": 0,
"doctype": "Notification",
"document_type": "LMS Payment",
"enabled": 1,
"event": "Days After",
"idx": 0,
"is_standard": 1,
"message": "<div>\n <p>Hi {{ doc.member_name }},</p>\n <p>We noticed that you started enrolling in the {{ doc.payment_for_document_type.split(\" \")[-1] }} {{ frappe.db.get_value(doc.payment_for_document_type, doc.payment_for_document, \"title\") }} but didn\u2019t complete your payment.</p>\n <p>We have a limited number of seats, and they won't be available for long!</p>\n <p>Don\u2019t miss this opportunity to enhance your skills. Click below to complete your enrollment:</p>\n <p>\n <a href=\"/lms/billing/{{ doc.payment_for_document_type.split(' ')[-1].lower() }}/{{ doc.payment_for_document }}\">\ud83d\udc49 Complete Your Enrollment</a>\n </p>\n <p>If you have any questions or need assistance, feel free to reach out to our support team.</p>\n <p>Looking forward to seeing you enrolled!</p>\n</div>",
"message_type": "HTML",
"minutes_offset": 0,
"modified": "2025-02-03 16:14:24.568958",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "Payment Completion Reminder",
"owner": "sayali@frappe.io",
"recipients": [
{
"receiver_by_document_field": "member"
},
{
"cc": "",
"receiver_by_role": "Moderator"
}
],
"send_system_notification": 0,
"send_to_all_assignees": 0,
"subject": " Complete Your Enrollment - Don't miss out!"
}

View File

@@ -1 +0,0 @@
<p>Add your message here</p>

View File

@@ -1,6 +0,0 @@
import frappe
def get_context(context):
# do your magic here
pass

View File

@@ -1,11 +0,0 @@
Hi {{ doc.member_name }},
We noticed that you started enrolling in the {{ doc.payment_for_document_type.split(" ")[-1] }} {{ frappe.db.get_value(doc.payment_for_document_type, doc.payment_for_document, "title") }} but didnt complete your payment. We have limited number of seats and they won't be empty for long.
Dont miss this opportunity to enhance your skills. Click below to complete your enrollment now:
[👉 Complete Your Enrollment](/lms/billing/{{ doc.payment_for_document_type.split(" ")[-1].lower()/doc.payment_for_document }})
If you have any questions or need assistance, feel free to reach out to our support team.
Looking forward to seeing you enrolled!

View File

@@ -1434,7 +1434,7 @@ def get_batch_students(batch):
""" Iterate through courses and track their progress """
for course in batch_courses:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": course.course, "member": student.student}, "progress"
"LMS Enrollment", {"course": course.course, "member": student.member}, "progress"
)
detail.courses[course.title] = progress
if progress == 100:
@@ -1445,11 +1445,12 @@ def get_batch_students(batch):
title = frappe.db.get_value(
assessment.assessment_type, assessment.assessment_name, "title"
)
status = has_submitted_assessment(
assessment.assessment_name, assessment.assessment_type, student.student
assessment_info = has_submitted_assessment(
assessment.assessment_name, assessment.assessment_type, student.member
)
detail.assessments[title] = status
if status not in ["Not Attempted", 0]:
detail.assessments[title] = assessment_info
if assessment_info.result == "Passed":
assessments_completed += 1
detail.courses_completed = courses_completed
@@ -1493,9 +1494,28 @@ def has_submitted_assessment(assessment, assessment_type, member=None):
attempt = frappe.db.exists(doctype, filters)
if attempt:
attempt_details = frappe.db.get_value(doctype, filters, fields)
return attempt_details
if assessment_type == "LMS Quiz":
result = "Failed"
passing_percentage = frappe.db.get_value(
"LMS Quiz", assessment, "passing_percentage"
)
if attempt_details >= passing_percentage:
result = "Passed"
else:
result = attempt_details
return frappe._dict(
{
"status": attempt_details,
"result": result,
}
)
else:
return not_attempted
return frappe._dict(
{
"status": not_attempted,
"result": "Failed",
}
)
@frappe.whitelist()

View File

@@ -98,4 +98,5 @@ lms.patches.v2_0.update_desk_access_for_lms_roles
lms.patches.v2_0.update_quiz_submission_data
lms.patches.v2_0.convert_quiz_duration_to_minutes
lms.patches.v2_0.allow_guest_access #05-02-2025
lms.patches.v2_0.migrate_batch_student_data #10-02-2025
lms.patches.v2_0.migrate_batch_student_data #10-02-2025
lms.patches.v2_0.delete_old_enrollment_doctypes

View File

@@ -0,0 +1,6 @@
import frappe
def execute():
frappe.delete_doc("DocType", "Batch Student")
frappe.delete_doc("Notification", "Payment Completion Reminder")

View File

@@ -0,0 +1,19 @@
<div>
<p>{{ _('Hi') }} {{ billing_name }},</p>
<p>{{ _('We noticed that you started enrolling in the') }} {{ type }} {{ title }} {{ _('but didnt complete your payment') }}.</p>
<p>
{{ _("We have a limited number of seats, and they won't be available for long!")}}
</p>
<p>
{{ _("Dont miss this opportunity to enhance your skills. Click below to complete your enrollment") }}:
</p>
<p>
<a href="{{ link }}">👉 Complete Your Enrollment</a>
</p>
<p>
{{ _("If you have any questions or need assistance, feel free to reach out to our support team.") }}
</p>
<p>
{{ __("Looking forward to seeing you enrolled!") }}
</p>
</div>