diff --git a/frontend/src/pages/SCORMChapter.vue b/frontend/src/pages/SCORMChapter.vue
index c11a6466..27b18036 100644
--- a/frontend/src/pages/SCORMChapter.vue
+++ b/frontend/src/pages/SCORMChapter.vue
@@ -12,7 +12,10 @@
user.data?.is_instructor)
"
>
-
+
@@ -49,6 +52,12 @@ const { brand } = sessionStore()
const sidebarStore = useSidebar()
const user = inject('$user')
const readyToRender = ref(false)
+const isSuccessfullyCompleted = ref(false)
+
+// If courseRestartOnFailure is true, student has to restart the whole course if failed.
+// Otherwise, student could retake the final quiz portion.
+// Ideally, this should be configurable along with `Number of failures before course should restart`.
+const courseRestartOnFailure = false
const props = defineProps({
courseName: {
@@ -88,25 +97,51 @@ const enrollment = createListResource({
})
const getDataFromLMS = (key) => {
- if (key == 'cmi.core.lesson_status') {
- if (progress.data?.status == 'Complete') {
- return 'passed'
- }
- return 'incomplete'
+ if (key === 'cmi.core.lesson_status') {
+ return progress.data?.status === 'Complete' ? 'passed' : 'incomplete'
+ } else if (key === 'cmi.launch_data') {
+ return progress.data?.scorm_content || ''
+ } else if (key === 'cmi.suspend_data') {
+ return progress.data?.scorm_content || ''
}
return ''
}
+let saveTimeout = null
+const debouncedSaveProgress = (scormDetails) => {
+ clearTimeout(saveTimeout)
+ saveTimeout = setTimeout(() => {
+ saveProgress(scormDetails)
+ }, 300)
+}
+
const saveDataToLMS = (key, value) => {
- if (key == 'cmi.core.lesson_status' && value == 'passed') {
- saveProgress()
+ if (key === 'cmi.core.lesson_status') {
+ if (value === 'passed') {
+ isSuccessfullyCompleted.value = true
+ saveProgress({
+ is_complete: isSuccessfullyCompleted.value,
+ scorm_content: '',
+ })
+ } else if (value === 'failed' && courseRestartOnFailure) {
+ saveProgress({
+ is_complete: isSuccessfullyCompleted.value,
+ scorm_content: '',
+ })
+ }
+ } else if (key === 'cmi.suspend_data' && !isSuccessfullyCompleted.value) {
+ debouncedSaveProgress({
+ is_complete: false,
+ scorm_content: value,
+ })
}
}
-const saveProgress = () => {
+const saveProgress = (scormDetails = null) => {
call('lms.lms.doctype.course_lesson.course_lesson.save_progress', {
lesson: chapter.doc.lessons[0].lesson,
course: props.courseName,
+ scorm_details: scormDetails,
})
}
@@ -115,7 +150,7 @@ const progress = createResource({
makeParams(values) {
return {
doctype: 'LMS Course Progress',
- fieldname: 'status',
+ fieldname: ['status', 'scorm_content'],
filters: {
member: user.data?.name,
lesson: chapter.doc.lessons[0].lesson,
diff --git a/lms/lms/api.py b/lms/lms/api.py
index 9c019509..49f750c3 100644
--- a/lms/lms/api.py
+++ b/lms/lms/api.py
@@ -1193,7 +1193,7 @@ def fetch_activity_data(member, start_date):
lesson_completions = frappe.get_all(
"LMS Course Progress",
fields=["creation"],
- filters={"member": member, "creation": [">=", start_date]},
+ filters={"member": member, "creation": [">=", start_date], "status": "Complete"},
)
quiz_submissions = frappe.get_all(
diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py
index d68bab8c..e077ba5b 100644
--- a/lms/lms/doctype/course_lesson/course_lesson.py
+++ b/lms/lms/doctype/course_lesson/course_lesson.py
@@ -46,20 +46,30 @@ class CourseLesson(Document):
@frappe.whitelist()
-def save_progress(lesson, course):
+def save_progress(lesson, course, scorm_details=None):
+ """
+ Note: Pass the argument scorm_details as a dict if it is SCORM related save_progress
+ """
membership = frappe.db.exists("LMS Enrollment", {"course": course, "member": frappe.session.user})
if not membership:
return 0
frappe.db.set_value("LMS Enrollment", membership, "current_lesson", lesson)
- already_completed = frappe.db.exists(
+ progress_already_exists = frappe.db.exists(
"LMS Course Progress", {"lesson": lesson, "member": frappe.session.user}
)
+ lesson_already_completed = frappe.db.exists(
+ "LMS Course Progress",
+ {"lesson": lesson, "member": frappe.session.user, "status": "Complete"},
+ )
quiz_completed = get_quiz_progress(lesson)
assignment_completed = get_assignment_progress(lesson)
- if not already_completed and quiz_completed and assignment_completed:
+ if scorm_details:
+ scorm_details = frappe._dict(**scorm_details)
+
+ if not progress_already_exists and quiz_completed and assignment_completed and not scorm_details:
frappe.get_doc(
{
"doctype": "LMS Course Progress",
@@ -68,6 +78,29 @@ def save_progress(lesson, course):
"member": frappe.session.user,
}
).save(ignore_permissions=True)
+ elif scorm_details and not lesson_already_completed and not progress_already_exists:
+ # Create new SCORM progress
+ frappe.get_doc(
+ {
+ "doctype": "LMS Course Progress",
+ "lesson": lesson,
+ "status": "Complete" if scorm_details.is_complete else "Partially Complete",
+ "member": frappe.session.user,
+ "scorm_content": "" if scorm_details.is_complete else scorm_details.scorm_content,
+ }
+ ).save(ignore_permissions=True)
+ elif scorm_details and not lesson_already_completed and progress_already_exists:
+ # Update Existing SCORM Progress
+ frappe.db.set_value(
+ "LMS Course Progress",
+ progress_already_exists,
+ {
+ "lesson": lesson,
+ "status": "Complete" if scorm_details.is_complete else "Partially Complete",
+ "member": frappe.session.user,
+ "scorm_content": "" if scorm_details.is_complete else scorm_details.scorm_content,
+ },
+ )
progress = get_course_progress(course)
capture_progress_for_analytics(progress, course)
diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py
index 8b880ffc..07752df8 100644
--- a/lms/lms/doctype/lms_batch/lms_batch.py
+++ b/lms/lms/doctype/lms_batch/lms_batch.py
@@ -270,7 +270,11 @@ def get_timetable_details(timetable):
True
if frappe.db.exists(
"LMS Course Progress",
- {"lesson": entry.reference_docname, "member": frappe.session.user},
+ {
+ "lesson": entry.reference_docname,
+ "member": frappe.session.user,
+ "status": "Complete",
+ },
)
else False
)
diff --git a/lms/lms/doctype/lms_course_progress/lms_course_progress.json b/lms/lms/doctype/lms_course_progress/lms_course_progress.json
index b05e1dd2..a0c89f76 100644
--- a/lms/lms/doctype/lms_course_progress/lms_course_progress.json
+++ b/lms/lms/doctype/lms_course_progress/lms_course_progress.json
@@ -11,7 +11,11 @@
"column_break_3",
"lesson",
"chapter",
- "course"
+ "course",
+ "section_break_uoob",
+ "is_scorm_chapter",
+ "column_break_wskp",
+ "scorm_content"
],
"fields": [
{
@@ -68,11 +72,36 @@
"fieldtype": "Data",
"label": "Member Name",
"read_only": 1
+ },
+ {
+ "fieldname": "section_break_uoob",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "fetch_from": "chapter.is_scorm_package",
+ "fetch_if_empty": 1,
+ "fieldname": "is_scorm_chapter",
+ "fieldtype": "Check",
+ "label": "Is SCORM Chapter",
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "column_break_wskp",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval: doc.is_scorm_chapter == 1 && doc.status == 'Partially Complete'",
+ "fieldname": "scorm_content",
+ "fieldtype": "Long Text",
+ "label": "SCORM Content",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2025-01-17 15:54:34.040621",
+ "modified": "2025-01-20 22:25:36.829929",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course Progress",
@@ -106,4 +135,4 @@
"states": [],
"title_field": "member_name",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/lms/lms/utils.py b/lms/lms/utils.py
index 54011eaf..c978cfcd 100644
--- a/lms/lms/utils.py
+++ b/lms/lms/utils.py
@@ -325,7 +325,7 @@ def get_progress(course, lesson, member=None):
return frappe.db.exists(
"LMS Course Progress",
- {"course": course, "member": member, "lesson": lesson},
+ {"course": course, "member": member, "lesson": lesson, "status": "Complete"},
["status"],
)
diff --git a/lms/templates/statistics.html b/lms/templates/statistics.html
index c33d6d1e..816b1c75 100644
--- a/lms/templates/statistics.html
+++ b/lms/templates/statistics.html
@@ -31,7 +31,7 @@
{% endif %} {% if lesson_completion %} {% set lesson_completion_count =
- frappe.db.count("LMS Course Progress") %}
+ frappe.db.count("LMS Course Progress", { "status": "Complete" }) %}