From f43331967cfabf4038921dd7c65125684c809a48 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 11 Feb 2025 13:28:49 +0530 Subject: [PATCH] fix: misc batch enrollment issues --- frontend/src/components/BatchStudents.vue | 2 +- frontend/src/components/Quiz.vue | 3 + frontend/src/pages/ProfileCertificates.vue | 2 +- lms/hooks.py | 5 +- lms/lms/doctype/batch_student/__init__.py | 0 .../doctype/batch_student/batch_student.js | 7 -- .../doctype/batch_student/batch_student.json | 83 -------------- .../doctype/batch_student/batch_student.py | 9 -- .../batch_student/test_batch_student.py | 9 -- .../lms_batch_enrollment.js | 22 +++- .../lms_batch_enrollment.py | 103 ++++++++++-------- lms/lms/doctype/lms_payment/lms_payment.py | 71 +++++++++++- .../doctype/lms_settings/lms_settings.json | 11 +- .../payment_completion_reminder/__init__.py | 0 .../payment_completion_reminder.html | 11 -- .../payment_completion_reminder.json | 35 ------ .../payment_completion_reminder.md | 1 - .../payment_completion_reminder.py | 6 - .../payment_completion_reminder.txt | 11 -- lms/lms/utils.py | 34 ++++-- lms/patches.txt | 3 +- .../v2_0/delete_old_enrollment_doctypes.py | 6 + lms/templates/emails/payment_reminder.html | 19 ++++ 23 files changed, 214 insertions(+), 239 deletions(-) delete mode 100644 lms/lms/doctype/batch_student/__init__.py delete mode 100644 lms/lms/doctype/batch_student/batch_student.js delete mode 100644 lms/lms/doctype/batch_student/batch_student.json delete mode 100644 lms/lms/doctype/batch_student/batch_student.py delete mode 100644 lms/lms/doctype/batch_student/test_batch_student.py delete mode 100644 lms/lms/notification/payment_completion_reminder/__init__.py delete mode 100644 lms/lms/notification/payment_completion_reminder/payment_completion_reminder.html delete mode 100644 lms/lms/notification/payment_completion_reminder/payment_completion_reminder.json delete mode 100644 lms/lms/notification/payment_completion_reminder/payment_completion_reminder.md delete mode 100644 lms/lms/notification/payment_completion_reminder/payment_completion_reminder.py delete mode 100644 lms/lms/notification/payment_completion_reminder/payment_completion_reminder.txt create mode 100644 lms/patches/v2_0/delete_old_enrollment_doctypes.py create mode 100644 lms/templates/emails/payment_reminder.html diff --git a/frontend/src/components/BatchStudents.vue b/frontend/src/components/BatchStudents.vue index 18a1ed92..c061c7cd 100644 --- a/frontend/src/components/BatchStudents.vue +++ b/frontend/src/components/BatchStudents.vue @@ -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 } }) diff --git a/frontend/src/components/Quiz.vue b/frontend/src/components/Quiz.vue index b8a4b743..62f0bdf7 100644 --- a/frontend/src/components/Quiz.vue +++ b/frontend/src/components/Quiz.vue @@ -316,6 +316,9 @@ const quiz = createResource({ }, cache: ['quiz', props.quizName], auto: true, + transform(data) { + data.duration = parseInt(data.duration) + }, onSuccess(data) { populateQuestions() setupTimer() diff --git a/frontend/src/pages/ProfileCertificates.vue b/frontend/src/pages/ProfileCertificates.vue index a8863fd2..3213f70e 100644 --- a/frontend/src/pages/ProfileCertificates.vue +++ b/frontend/src/pages/ProfileCertificates.vue @@ -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], }) diff --git a/lms/hooks.py b/lms/hooks.py index b00d4b93..e1ec5c55 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -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"] diff --git a/lms/lms/doctype/batch_student/__init__.py b/lms/lms/doctype/batch_student/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lms/lms/doctype/batch_student/batch_student.js b/lms/lms/doctype/batch_student/batch_student.js deleted file mode 100644 index 680bc334..00000000 --- a/lms/lms/doctype/batch_student/batch_student.js +++ /dev/null @@ -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) { - // } -}); diff --git a/lms/lms/doctype/batch_student/batch_student.json b/lms/lms/doctype/batch_student/batch_student.json deleted file mode 100644 index bdd5338e..00000000 --- a/lms/lms/doctype/batch_student/batch_student.json +++ /dev/null @@ -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": [] -} \ No newline at end of file diff --git a/lms/lms/doctype/batch_student/batch_student.py b/lms/lms/doctype/batch_student/batch_student.py deleted file mode 100644 index 002a6f61..00000000 --- a/lms/lms/doctype/batch_student/batch_student.py +++ /dev/null @@ -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 diff --git a/lms/lms/doctype/batch_student/test_batch_student.py b/lms/lms/doctype/batch_student/test_batch_student.py deleted file mode 100644 index 8a5e8bb1..00000000 --- a/lms/lms/doctype/batch_student/test_batch_student.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2022, Frappe and Contributors -# See license.txt - -# import frappe -from frappe.tests import UnitTestCase - - -class TestBatchStudent(UnitTestCase): - pass diff --git a/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.js b/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.js index eca910fc..fb8d174e 100644 --- a/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.js +++ b/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.js @@ -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(); + }, + }); + }); + } + }, +}); diff --git a/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py b/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py index 2ca34f9f..7049a104 100644 --- a/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py +++ b/lms/lms/doctype/lms_batch_enrollment/lms_batch_enrollment.py @@ -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, + ) diff --git a/lms/lms/doctype/lms_payment/lms_payment.py b/lms/lms/doctype/lms_payment/lms_payment.py index a250fd99..d8e25f77 100644 --- a/lms/lms/doctype/lms_payment/lms_payment.py +++ b/lms/lms/doctype/lms_payment/lms_payment.py @@ -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, + ) diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 86252fa1..8f7d3be2 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -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", diff --git a/lms/lms/notification/payment_completion_reminder/__init__.py b/lms/lms/notification/payment_completion_reminder/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.html b/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.html deleted file mode 100644 index 179e4856..00000000 --- a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.html +++ /dev/null @@ -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 didn’t complete your payment.

-

We have a limited number of seats, and they won't be available for long!

-

Don’t miss this opportunity to enhance your skills. Click below to complete your enrollment:

-

- 👉 Complete Your Enrollment -

-

If you have any questions or need assistance, feel free to reach out to our support team.

-

Looking forward to seeing you enrolled!

-
\ No newline at end of file diff --git a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.json b/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.json deleted file mode 100644 index 26860544..00000000 --- a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.json +++ /dev/null @@ -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": "
\n

Hi {{ doc.member_name }},

\n

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.

\n

We have a limited number of seats, and they won't be available for long!

\n

Don\u2019t miss this opportunity to enhance your skills. Click below to complete your enrollment:

\n

\n \ud83d\udc49 Complete Your Enrollment\n

\n

If you have any questions or need assistance, feel free to reach out to our support team.

\n

Looking forward to seeing you enrolled!

\n
", - "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!" -} \ No newline at end of file diff --git a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.md b/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.md deleted file mode 100644 index f96f7f9a..00000000 --- a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.md +++ /dev/null @@ -1 +0,0 @@ -

Add your message here

diff --git a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.py b/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.py deleted file mode 100644 index 80b7b873..00000000 --- a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.py +++ /dev/null @@ -1,6 +0,0 @@ -import frappe - - -def get_context(context): - # do your magic here - pass diff --git a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.txt b/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.txt deleted file mode 100644 index 28e1c47e..00000000 --- a/lms/lms/notification/payment_completion_reminder/payment_completion_reminder.txt +++ /dev/null @@ -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 didn’t complete your payment. We have limited number of seats and they won't be empty for long. - -Don’t 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! diff --git a/lms/lms/utils.py b/lms/lms/utils.py index b8b1d5a9..c7a82860 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -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() diff --git a/lms/patches.txt b/lms/patches.txt index d3f6ba5b..2fd46b79 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -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 \ No newline at end of file +lms.patches.v2_0.migrate_batch_student_data #10-02-2025 +lms.patches.v2_0.delete_old_enrollment_doctypes \ No newline at end of file diff --git a/lms/patches/v2_0/delete_old_enrollment_doctypes.py b/lms/patches/v2_0/delete_old_enrollment_doctypes.py new file mode 100644 index 00000000..8d694a90 --- /dev/null +++ b/lms/patches/v2_0/delete_old_enrollment_doctypes.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + frappe.delete_doc("DocType", "Batch Student") + frappe.delete_doc("Notification", "Payment Completion Reminder") diff --git a/lms/templates/emails/payment_reminder.html b/lms/templates/emails/payment_reminder.html new file mode 100644 index 00000000..dd2847be --- /dev/null +++ b/lms/templates/emails/payment_reminder.html @@ -0,0 +1,19 @@ +
+

{{ _('Hi') }} {{ billing_name }},

+

{{ _('We noticed that you started enrolling in the') }} {{ type }} {{ title }} {{ _('but didn’t complete your payment') }}.

+

+ {{ _("We have a limited number of seats, and they won't be available for long!")}} +

+

+ {{ _("Don’t miss this opportunity to enhance your skills. Click below to complete your enrollment") }}: +

+

+ 👉 Complete Your Enrollment +

+

+ {{ _("If you have any questions or need assistance, feel free to reach out to our support team.") }} +

+

+ {{ __("Looking forward to seeing you enrolled!") }} +

+
\ No newline at end of file