import('@/pages/Lesson.vue'),
props: true,
},
+ {
+ path: '/courses/:courseName/certification',
+ name: 'CourseCertification',
+ component: () => import('@/pages/CourseCertification.vue'),
+ props: true,
+ },
{
path: '/courses/:courseName/learn/:chapterName',
name: 'SCORMChapter',
diff --git a/lms/lms/api.py b/lms/lms/api.py
index 74daf2e1..9d4c930c 100644
--- a/lms/lms/api.py
+++ b/lms/lms/api.py
@@ -190,24 +190,24 @@ def get_translations():
@frappe.whitelist()
-def validate_billing_access(type, name):
+def validate_billing_access(billing_type, name):
access = True
message = ""
- doctype = "LMS Course" if type == "course" else "LMS Batch"
+ doctype = "LMS Batch" if billing_type == "batch" else "LMS Course"
if frappe.session.user == "Guest":
access = False
message = _("Please login to continue with payment.")
- if type not in ["course", "batch"]:
+ if access and billing_type not in ["course", "batch", "certificate"]:
access = False
message = _("Module is incorrect.")
- if not frappe.db.exists(doctype, name):
+ if access and not frappe.db.exists(doctype, name):
access = False
message = _("Module Name is incorrect or does not exist.")
- if type == "course":
+ if access and billing_type == "course":
membership = frappe.db.exists(
"LMS Enrollment", {"member": frappe.session.user, "course": name}
)
@@ -215,7 +215,7 @@ def validate_billing_access(type, name):
access = False
message = _("You are already enrolled for this course.")
- else:
+ elif access and billing_type == "batch":
membership = frappe.db.exists(
"LMS Batch Enrollment", {"member": frappe.session.user, "batch": name}
)
@@ -223,6 +223,19 @@ def validate_billing_access(type, name):
access = False
message = _("You are already enrolled for this batch.")
+ elif access and billing_type == "certificate":
+ purchased_certificate = frappe.db.exists(
+ "LMS Enrollment",
+ {
+ "course": name,
+ "member": frappe.session.user,
+ "purchased_certificate": 1,
+ },
+ )
+ if purchased_certificate:
+ access = False
+ message = _("You have already purchased the certificate for this course.")
+
address = frappe.db.get_value(
"Address",
{"email_id": frappe.session.user},
@@ -370,7 +383,7 @@ def get_evaluator_details(evaluator):
@frappe.whitelist(allow_guest=True)
-def get_certified_participants(filters=None, start=0, page_length=30, search=None):
+def get_certified_participants(filters=None, start=0, page_length=30):
or_filters = {}
if not filters:
filters = {}
diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py
index dfb7f4fe..e293c794 100644
--- a/lms/lms/doctype/lms_batch/lms_batch.py
+++ b/lms/lms/doctype/lms_batch/lms_batch.py
@@ -10,7 +10,6 @@ from datetime import timedelta
from frappe.model.document import Document
from frappe.utils import cint, format_datetime, get_time, add_days, nowdate
from lms.lms.utils import (
- get_lessons,
get_lesson_index,
get_lesson_url,
get_quiz_details,
@@ -258,17 +257,6 @@ def create_batch(
return doc
-@frappe.whitelist()
-def fetch_lessons(courses):
- lessons = []
- courses = json.loads(courses)
-
- for course in courses:
- lessons.extend(get_lessons(course.get("course")))
-
- return lessons
-
-
@frappe.whitelist()
def add_course(course, parent, name=None, evaluator=None):
frappe.only_for("Moderator")
diff --git a/lms/lms/doctype/lms_course/lms_course.json b/lms/lms/doctype/lms_course/lms_course.json
index beae4cd0..9369456c 100644
--- a/lms/lms/doctype/lms_course/lms_course.json
+++ b/lms/lms/doctype/lms_course/lms_course.json
@@ -40,15 +40,12 @@
"pricing_tab",
"pricing_section",
"paid_course",
+ "enable_certification",
+ "paid_certificate",
"column_break_acoj",
"course_price",
"currency",
"amount_usd",
- "certification_tab",
- "certification_section",
- "enable_certification",
- "column_break_rxww",
- "expiry",
"tab_4_tab",
"statistics_section",
"enrollments",
@@ -134,22 +131,11 @@
"fieldtype": "Section Break",
"label": "Course Settings"
},
- {
- "fieldname": "certification_section",
- "fieldtype": "Section Break"
- },
{
"default": "0",
"fieldname": "enable_certification",
"fieldtype": "Check",
- "label": "Enable Certification"
- },
- {
- "default": "0",
- "depends_on": "enable_certification",
- "fieldname": "expiry",
- "fieldtype": "Int",
- "label": "Certification Expires After (Years)"
+ "label": "Completion Certificate"
},
{
"fieldname": "related_courses",
@@ -181,7 +167,6 @@
"fieldtype": "Section Break"
},
{
- "depends_on": "paid_course",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -195,22 +180,16 @@
"label": "Paid Course"
},
{
- "depends_on": "paid_course",
"fieldname": "course_price",
"fieldtype": "Currency",
- "label": "Course Price",
+ "label": "Amount",
"mandatory_depends_on": "paid_course"
},
- {
- "fieldname": "column_break_rxww",
- "fieldtype": "Column Break"
- },
{
"fieldname": "column_break_acoj",
"fieldtype": "Column Break"
},
{
- "depends_on": "paid_course",
"description": "If you set an amount here, then the USD equivalent setting will not get applied.",
"fieldname": "amount_usd",
"fieldtype": "Currency",
@@ -238,12 +217,7 @@
{
"fieldname": "pricing_tab",
"fieldtype": "Tab Break",
- "label": "Pricing"
- },
- {
- "fieldname": "certification_tab",
- "fieldtype": "Tab Break",
- "label": "Certification"
+ "label": "Pricing and Certification"
},
{
"fieldname": "column_break_htgn",
@@ -284,6 +258,12 @@
"fieldtype": "Data",
"label": "Rating",
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "paid_certificate",
+ "fieldtype": "Check",
+ "label": "Paid Certificate"
}
],
"is_published_field": "published",
@@ -310,7 +290,7 @@
}
],
"make_attachments_public": 1,
- "modified": "2024-10-30 23:08:31.842860",
+ "modified": "2025-02-20 16:44:38.891383",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course",
diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py
index 1870130a..54625d49 100644
--- a/lms/lms/doctype/lms_course/lms_course.py
+++ b/lms/lms/doctype/lms_course/lms_course.py
@@ -5,9 +5,8 @@ import json
import random
import frappe
from frappe.model.document import Document
-from frappe.utils import cint, today
-from frappe.utils.telemetry import capture
-from lms.lms.utils import get_chapters, can_create_courses
+from frappe.utils import today, cint
+from lms.lms.utils import get_chapters
from ...utils import generate_slug, validate_image, update_payment_record
from frappe import _
@@ -53,9 +52,12 @@ class LMSCourse(Document):
frappe.throw(_("Please install the Payments app to create a paid courses."))
def validate_amount_and_currency(self):
- if self.paid_course and (not self.course_price and not self.currency):
+ if self.paid_course and (cint(self.course_price) < 0 or not self.currency):
frappe.throw(_("Amount and currency are required for paid courses."))
+ if self.paid_certificate and (cint(self.course_price) <= 0 or not self.currency):
+ frappe.throw(_("Amount and currency are required for paid certificates."))
+
def on_update(self):
if not self.upcoming and self.has_value_changed("upcoming"):
self.send_email_to_interested_users()
diff --git a/lms/lms/doctype/lms_enrollment/lms_enrollment.json b/lms/lms/doctype/lms_enrollment/lms_enrollment.json
index 0e9b258a..5a811ccf 100644
--- a/lms/lms/doctype/lms_enrollment/lms_enrollment.json
+++ b/lms/lms/doctype/lms_enrollment/lms_enrollment.json
@@ -14,6 +14,9 @@
"member",
"member_name",
"member_username",
+ "certification_section",
+ "purchased_certificate",
+ "certificate",
"section_break_8",
"cohort",
"subgroup",
@@ -123,11 +126,28 @@
"fieldtype": "Link",
"label": "Payment",
"options": "LMS Payment"
+ },
+ {
+ "fieldname": "certification_section",
+ "fieldtype": "Section Break",
+ "label": "Certification"
+ },
+ {
+ "default": "0",
+ "fieldname": "purchased_certificate",
+ "fieldtype": "Check",
+ "label": "Purchased Certificate"
+ },
+ {
+ "fieldname": "certificate",
+ "fieldtype": "Link",
+ "label": "Certificate",
+ "options": "LMS Certificate"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2024-10-30 12:44:16.103598",
+ "modified": "2025-02-21 17:11:37.986157",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Enrollment",
diff --git a/lms/lms/doctype/lms_payment/lms_payment.json b/lms/lms/doctype/lms_payment/lms_payment.json
index 67720a46..cca8ae66 100644
--- a/lms/lms/doctype/lms_payment/lms_payment.json
+++ b/lms/lms/doctype/lms_payment/lms_payment.json
@@ -14,6 +14,7 @@
"payment_for_document_type",
"payment_for_document",
"payment_received",
+ "payment_for_certificate",
"payment_details_section",
"currency",
"amount",
@@ -136,6 +137,12 @@
"fieldtype": "Link",
"label": "Source",
"options": "LMS Source"
+ },
+ {
+ "default": "0",
+ "fieldname": "payment_for_certificate",
+ "fieldtype": "Check",
+ "label": "Payment for Certificate"
}
],
"index_web_pages_for_search": 1,
@@ -149,7 +156,7 @@
"link_fieldname": "payment"
}
],
- "modified": "2025-02-18 15:54:25.383353",
+ "modified": "2025-02-21 18:29:55.436611",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Payment",
diff --git a/lms/lms/payments.py b/lms/lms/payments.py
index 8f8cc082..9f4e31fe 100644
--- a/lms/lms/payments.py
+++ b/lms/lms/payments.py
@@ -18,19 +18,26 @@ def validate_currency(payment_gateway, currency):
@frappe.whitelist()
-def get_payment_link(doctype, docname, title, amount, total_amount, currency, address):
+def get_payment_link(
+ doctype,
+ docname,
+ title,
+ amount,
+ total_amount,
+ currency,
+ address,
+ redirect_to,
+ payment_for_certificate,
+):
payment_gateway = get_payment_gateway()
address = frappe._dict(address)
amount_with_gst = total_amount if total_amount != amount else 0
- payment = record_payment(address, doctype, docname, amount, currency, amount_with_gst)
+ payment = record_payment(
+ address, doctype, docname, amount, currency, amount_with_gst, payment_for_certificate
+ )
controller = get_controller(payment_gateway)
- if doctype == "LMS Course":
- redirect_to = f"/lms/courses/{docname}/learn/1-1"
- elif doctype == "LMS Batch":
- redirect_to = f"/lms/batches/{docname}"
-
payment_details = {
"amount": total_amount,
"title": f"Payment for {doctype} {title} {docname}",
@@ -53,7 +60,15 @@ def get_payment_link(doctype, docname, title, amount, total_amount, currency, ad
return url
-def record_payment(address, doctype, docname, amount, currency, amount_with_gst=0):
+def record_payment(
+ address,
+ doctype,
+ docname,
+ amount,
+ currency,
+ amount_with_gst=0,
+ payment_for_certificate=0,
+):
address = frappe._dict(address)
address_name = save_address(address)
@@ -71,6 +86,7 @@ def record_payment(address, doctype, docname, amount, currency, amount_with_gst=
"source": address.source,
"payment_for_document_type": doctype,
"payment_for_document": docname,
+ "payment_for_certificate": payment_for_certificate,
}
)
payment_doc.save(ignore_permissions=True)
diff --git a/lms/lms/utils.py b/lms/lms/utils.py
index 5a6147b9..9405fd50 100644
--- a/lms/lms/utils.py
+++ b/lms/lms/utils.py
@@ -68,27 +68,26 @@ def generate_slug(title, doctype):
return slugify(title, used_slugs=slugs)
-def get_membership(course, member=None, batch=None):
+def get_membership(course, member=None):
if not member:
member = frappe.session.user
filters = {"member": member, "course": course}
- if batch:
- filters["batch_old"] = batch
- is_member = frappe.db.exists("LMS Enrollment", filters)
- if is_member:
+ if frappe.db.exists("LMS Enrollment", filters):
membership = frappe.db.get_value(
"LMS Enrollment",
filters,
- ["name", "batch_old", "current_lesson", "member_type", "progress", "member"],
+ [
+ "name",
+ "current_lesson",
+ "progress",
+ "member",
+ "purchased_certificate",
+ "certificate",
+ ],
as_dict=True,
)
-
- if membership and membership.batch_old:
- membership.batch_title = frappe.db.get_value(
- "LMS Batch Old", membership.batch_old, "title"
- )
return membership
return False
@@ -1009,6 +1008,7 @@ def get_course_details(course):
"category",
"status",
"paid_course",
+ "paid_certificate",
"course_price",
"currency",
"amount_usd",
@@ -1023,7 +1023,7 @@ def get_course_details(course):
course_details.instructors = get_instructors(course_details.name)
# course_details.is_instructor = is_instructor(course_details.name)
- if course_details.paid_course:
+ if course_details.paid_course or course_details.paid_certificate:
"""course_details.course_price, course_details.currency = check_multicurrency(
course_details.course_price, course_details.currency, None, course_details.amount_usd
)"""
@@ -1136,14 +1136,21 @@ def get_lesson(course, chapter, lesson):
return {}
membership = get_membership(course)
- course_title = frappe.db.get_value("LMS Course", course, "title")
+ course_info = frappe.db.get_value(
+ "LMS Course", course, ["title", "paid_certificate"], as_dict=1
+ )
+
if (
not lesson_details.include_in_preview
and not membership
and not has_course_moderator_role()
and not is_instructor(course)
):
- return {"no_preview": 1, "title": lesson_details.title, "course_title": course_title}
+ return {
+ "no_preview": 1,
+ "title": lesson_details.title,
+ "course_title": course_info.title,
+ }
lesson_details = frappe.db.get_value(
"Course Lesson",
@@ -1178,7 +1185,8 @@ def get_lesson(course, chapter, lesson):
lesson_details.prev = neighbours["prev"]
lesson_details.membership = membership
lesson_details.instructors = get_instructors(course)
- lesson_details.course_title = course_title
+ lesson_details.course_title = course_info.title
+ lesson_details.paid_certificate = course_info.paid_certificate
return lesson_details
@@ -1612,11 +1620,19 @@ def get_order_summary(doctype, docname, country=None):
details = frappe.db.get_value(
"LMS Course",
docname,
- ["title", "name", "paid_course", "course_price as amount", "currency", "amount_usd"],
+ [
+ "title",
+ "name",
+ "paid_course",
+ "paid_certificate",
+ "course_price as amount",
+ "currency",
+ "amount_usd",
+ ],
as_dict=True,
)
- if not details.paid_course:
+ if not details.paid_course and not details.paid_certificate:
raise frappe.throw(_("This course is free."))
else:
@@ -1730,9 +1746,14 @@ def update_payment_record(doctype, docname):
"order_id": data.get("order_id"),
},
)
+ payment_for_certificate = frappe.db.get_value(
+ "LMS Payment", data.payment, "payment_for_certificate"
+ )
try:
- if doctype == "LMS Course":
+ if payment_for_certificate:
+ update_certificate_purchase(docname)
+ elif doctype == "LMS Course":
enroll_in_course(data.payment, docname)
else:
enroll_in_batch(docname, data.payment)
@@ -1792,6 +1813,15 @@ def enroll_in_batch(batch, payment_name=None):
new_student.save()
+def update_certificate_purchase(course):
+ frappe.db.set_value(
+ "LMS Enrollment",
+ {"member": frappe.session.user, "course": course},
+ "purchased_certificate",
+ 1,
+ )
+
+
@frappe.whitelist()
def get_programs():
if (
diff --git a/lms/lms/widgets/CourseOutline.html b/lms/lms/widgets/CourseOutline.html
deleted file mode 100644
index b95fbdba..00000000
--- a/lms/lms/widgets/CourseOutline.html
+++ /dev/null
@@ -1,110 +0,0 @@
-{% set chapters = get_chapters(course.name) %}
-{% set is_instructor = is_instructor(course.name) %}
-
-{% if chapters | length %}
-
-
- {% if not lesson_page %}
-
- {{ _("Course Content") }}
-
-
-
- {% endif %}
-
- {% if chapters | length %}
-
- {% for chapter in chapters %}
- {% set lessons = get_lessons(course.name, chapter) %}
-
-
-
-
-
-

-
- {{ chapter.title }}
-
-
-
-
-
-
-
- {% if chapter.description %}
-
- {{ chapter.description }}
-
- {% endif %}
-
-
-
- {% if lessons | length %}
-
- {% for lesson in lessons %}
- {% set active = membership.current_lesson == lesson.name %}
-
- {% endfor %}
-
- {% endif %}
-
-
-
-
- {% endfor %}
-
-
- {% endif %}
-
-
-{% endif %}
-
-{% if chapters | length %}
-
-{{ widgets.NoPreviewModal(course=course, membership=membership) }}
-
-{% endif %}