@@ -2,7 +2,7 @@
|
|||||||
<div class="text-base">
|
<div class="text-base">
|
||||||
<div
|
<div
|
||||||
v-if="title && (outline.data?.length || allowEdit)"
|
v-if="title && (outline.data?.length || allowEdit)"
|
||||||
class="grid grid-cols-[70%,30%] mb-4 px-3"
|
class="grid grid-cols-[70%,30%] mb-4"
|
||||||
>
|
>
|
||||||
<div class="font-semibold text-lg">
|
<div class="font-semibold text-lg">
|
||||||
{{ __(title) }}
|
{{ __(title) }}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
{{ lesson.title }}
|
{{ lesson.title }}
|
||||||
<Check
|
<Check
|
||||||
v-if="lesson.is_complete"
|
v-if="lesson.is_complete"
|
||||||
class="h-4 w-4 text-green-500 stroke-1.5 ml-2"
|
class="h-4 w-4 text-green-700 ml-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, createResource } from 'frappe-ui'
|
import { Button, createResource } from 'frappe-ui'
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@@ -139,6 +139,10 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
getProgress: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const outline = createResource({
|
const outline = createResource({
|
||||||
@@ -146,6 +150,7 @@ const outline = createResource({
|
|||||||
cache: ['course_outline', props.courseName],
|
cache: ['course_outline', props.courseName],
|
||||||
params: {
|
params: {
|
||||||
course: props.courseName,
|
course: props.courseName,
|
||||||
|
progress: props.getProgress,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -151,7 +151,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sticky top-10">
|
<div class="sticky top-10">
|
||||||
<div class="bg-gray-50 py-5 pl-2 border-b">
|
<div class="bg-gray-50 py-5 px-2 border-b">
|
||||||
<div class="text-lg font-semibold">
|
<div class="text-lg font-semibold">
|
||||||
{{ lesson.data.course_title }}
|
{{ lesson.data.course_title }}
|
||||||
</div>
|
</div>
|
||||||
@@ -170,7 +170,11 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CourseOutline :courseName="courseName" :key="chapterNumber" />
|
<CourseOutline
|
||||||
|
:courseName="courseName"
|
||||||
|
:key="chapterNumber"
|
||||||
|
:getProgress="lesson.data.membership ? true : false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,12 +12,92 @@
|
|||||||
{{ __('No introduction') }}
|
{{ __('No introduction') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-7 mb-10">
|
||||||
|
<h2 class="mb-3 text-lg font-semibold text-gray-900">
|
||||||
|
{{ __('Achievements') }}
|
||||||
|
</h2>
|
||||||
|
<div class="grid grid-cols-5 gap-4">
|
||||||
|
<div v-if="badges.data" v-for="badge in badges.data">
|
||||||
|
<Popover trigger="hover">
|
||||||
|
<template #target>
|
||||||
|
<div class="relative">
|
||||||
|
<img
|
||||||
|
:src="badge.badge_image"
|
||||||
|
:alt="badge.badge"
|
||||||
|
class="h-[80px]"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="badge.count > 1"
|
||||||
|
class="flex items-end bg-gray-100 p-2 text-xs font-semibold rounded-full absolute right-0 bottom-0"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<X class="w-3 h-3" />
|
||||||
|
</span>
|
||||||
|
{{ badge.count }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body-main>
|
||||||
|
<div class="w-[250px] text-base">
|
||||||
|
<img
|
||||||
|
:src="badge.badge_image"
|
||||||
|
:alt="badge.badge"
|
||||||
|
class="bg-gray-100 rounded-t-md"
|
||||||
|
/>
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="text-2xl font-semibold mb-2">
|
||||||
|
{{ badge.badge }}
|
||||||
|
</div>
|
||||||
|
<div class="leading-5 mb-4">
|
||||||
|
{{ badge.badge_description }}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xs text-gray-700 font-medium mb-1">
|
||||||
|
{{ __('Issued on') }}:
|
||||||
|
</span>
|
||||||
|
{{ dayjs(badge.issued_on).format('DD MMM YYYY') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import { createResource, Popover } from 'frappe-ui'
|
||||||
|
import { X } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const dayjs = inject('$dayjs')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
profile: {
|
profile: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const badges = createResource({
|
||||||
|
url: 'frappe.client.get_list',
|
||||||
|
params: {
|
||||||
|
doctype: 'LMS Badge Assignment',
|
||||||
|
fields: ['name', 'badge', 'badge_image', 'badge_description', 'issued_on'],
|
||||||
|
filters: {
|
||||||
|
member: props.profile.data.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
transform(data) {
|
||||||
|
let finalBadges = []
|
||||||
|
let groupedBadges = Object.groupBy(data, ({ badge }) => badge)
|
||||||
|
for (let badge in groupedBadges) {
|
||||||
|
let badgeData = groupedBadges[badge][0]
|
||||||
|
badgeData.count = groupedBadges[badge].length
|
||||||
|
finalBadges.push(badgeData)
|
||||||
|
}
|
||||||
|
return finalBadges
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
50
lms/fixtures/lms_badge.json
Normal file
50
lms/fixtures/lms_badge.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"condition": "{\n \"parent\": \"CLS-03050\"\n}",
|
||||||
|
"description": "You have successfully completed the VueJs + Frappe UI training.",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Badge",
|
||||||
|
"enabled": 0,
|
||||||
|
"event": "Auto Assign",
|
||||||
|
"field_to_check": null,
|
||||||
|
"grant_only_once": 1,
|
||||||
|
"image": "/files/images.jpeg",
|
||||||
|
"modified": "2024-05-14 12:56:05.031313",
|
||||||
|
"name": "Batch Completion",
|
||||||
|
"reference_doctype": "Batch Student",
|
||||||
|
"title": "Batch Completion",
|
||||||
|
"user_field": "student"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "doc.progress == float(\"100.0\")",
|
||||||
|
"description": "You have completed your first course 👏",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Badge",
|
||||||
|
"enabled": 0,
|
||||||
|
"event": "Value Change",
|
||||||
|
"field_to_check": "progress",
|
||||||
|
"grant_only_once": 1,
|
||||||
|
"image": "/files/icon_badge-04.png",
|
||||||
|
"modified": "2024-05-14 12:56:15.469656",
|
||||||
|
"name": "Course Completion",
|
||||||
|
"reference_doctype": "LMS Enrollment",
|
||||||
|
"title": "Course Completion",
|
||||||
|
"user_field": "member"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"condition": "doc.percentage == 100",
|
||||||
|
"description": "Congratulations on getting a 100% score on a quiz.",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "LMS Badge",
|
||||||
|
"enabled": 0,
|
||||||
|
"event": "New",
|
||||||
|
"field_to_check": null,
|
||||||
|
"grant_only_once": 1,
|
||||||
|
"image": "/files/curiosity-badge-removebg-preview.png",
|
||||||
|
"modified": "2024-05-14 12:56:22.907584",
|
||||||
|
"name": "Quiz Completion",
|
||||||
|
"reference_doctype": "LMS Quiz Submission",
|
||||||
|
"title": "Quiz Completion",
|
||||||
|
"user_field": "member"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -97,6 +97,11 @@ override_doctype_class = {
|
|||||||
# Hook on document methods and events
|
# Hook on document methods and events
|
||||||
|
|
||||||
doc_events = {
|
doc_events = {
|
||||||
|
"*": {
|
||||||
|
"on_change": [
|
||||||
|
"lms.lms.doctype.lms_badge.lms_badge.process_badges",
|
||||||
|
]
|
||||||
|
},
|
||||||
"Discussion Reply": {"after_insert": "lms.lms.utils.handle_notifications"},
|
"Discussion Reply": {"after_insert": "lms.lms.utils.handle_notifications"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +113,7 @@ scheduler_events = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures = ["Custom Field", "Function", "Industry"]
|
fixtures = ["Custom Field", "Function", "Industry", "LMS Badge"]
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
# -------
|
# -------
|
||||||
|
|||||||
@@ -362,6 +362,21 @@ def get_certified_participants(search_query=""):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
def get_assigned_badges(member):
|
||||||
|
assigned_badges = frappe.get_all(
|
||||||
|
"LMS Badge Assignment",
|
||||||
|
{"member": member},
|
||||||
|
["badge"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
for badge in assigned_badges:
|
||||||
|
badge.update(
|
||||||
|
frappe.db.get_value("LMS Badge", badge.badge, ["name", "title", "image"])
|
||||||
|
)
|
||||||
|
return assigned_badges
|
||||||
|
|
||||||
|
|
||||||
def get_certificates(member):
|
def get_certificates(member):
|
||||||
"""Get certificates for a member."""
|
"""Get certificates for a member."""
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from frappe.model.document import Document
|
|||||||
from frappe.utils.telemetry import capture
|
from frappe.utils.telemetry import capture
|
||||||
from lms.lms.utils import get_course_progress
|
from lms.lms.utils import get_course_progress
|
||||||
from ...md import find_macros
|
from ...md import find_macros
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class CourseLesson(Document):
|
class CourseLesson(Document):
|
||||||
@@ -88,8 +89,9 @@ class CourseLesson(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def save_progress(lesson, course):
|
def save_progress(lesson, course):
|
||||||
|
print("save progress")
|
||||||
membership = frappe.db.exists(
|
membership = frappe.db.exists(
|
||||||
"LMS Enrollment", {"member": frappe.session.user, "course": course}
|
"LMS Enrollment", {"course": course, "member": frappe.session.user}
|
||||||
)
|
)
|
||||||
if not membership:
|
if not membership:
|
||||||
return 0
|
return 0
|
||||||
@@ -114,23 +116,52 @@ def save_progress(lesson, course):
|
|||||||
|
|
||||||
progress = get_course_progress(course)
|
progress = get_course_progress(course)
|
||||||
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
||||||
|
enrollment = frappe.get_doc("LMS Enrollment", membership)
|
||||||
|
enrollment.run_method("on_change")
|
||||||
return progress
|
return progress
|
||||||
|
|
||||||
|
|
||||||
def get_quiz_progress(lesson):
|
def get_quiz_progress(lesson):
|
||||||
body = frappe.db.get_value("Course Lesson", lesson, "body")
|
lesson_details = frappe.db.get_value(
|
||||||
macros = find_macros(body)
|
"Course Lesson", lesson, ["body", "content"], as_dict=1
|
||||||
quizzes = [value for name, value in macros if name == "Quiz"]
|
)
|
||||||
|
quizzes = []
|
||||||
|
|
||||||
|
if lesson_details.content:
|
||||||
|
content = json.loads(lesson_details.content)
|
||||||
|
|
||||||
|
for block in content.get("blocks"):
|
||||||
|
if block.get("type") == "quiz":
|
||||||
|
quizzes.append(block.get("data").get("quiz"))
|
||||||
|
|
||||||
|
elif lesson_details.body:
|
||||||
|
macros = find_macros(lesson_details.body)
|
||||||
|
quizzes = [value for name, value in macros if name == "Quiz"]
|
||||||
|
|
||||||
for quiz in quizzes:
|
for quiz in quizzes:
|
||||||
|
print(quiz)
|
||||||
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
passing_percentage = frappe.db.get_value("LMS Quiz", quiz, "passing_percentage")
|
||||||
|
print(frappe.session.user)
|
||||||
|
print(passing_percentage)
|
||||||
|
print(
|
||||||
|
frappe.db.exists(
|
||||||
|
"LMS Quiz Submission",
|
||||||
|
{
|
||||||
|
"quiz": quiz,
|
||||||
|
"member": frappe.session.user,
|
||||||
|
"percentage": [">=", passing_percentage],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
if not frappe.db.exists(
|
if not frappe.db.exists(
|
||||||
"LMS Quiz Submission",
|
"LMS Quiz Submission",
|
||||||
{
|
{
|
||||||
"quiz": quiz,
|
"quiz": quiz,
|
||||||
"owner": frappe.session.user,
|
"member": frappe.session.user,
|
||||||
"percentage": [">=", passing_percentage],
|
"percentage": [">=", passing_percentage],
|
||||||
},
|
},
|
||||||
):
|
):
|
||||||
|
print("no submission")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
0
lms/lms/doctype/lms_badge/__init__.py
Normal file
0
lms/lms/doctype/lms_badge/__init__.py
Normal file
63
lms/lms/doctype/lms_badge/lms_badge.js
Normal file
63
lms/lms/doctype/lms_badge/lms_badge.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2024, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("LMS Badge", {
|
||||||
|
refresh: (frm) => {
|
||||||
|
frm.events.set_field_options(frm);
|
||||||
|
|
||||||
|
if (frm.doc.event == "Auto Assign") {
|
||||||
|
add_assign_button(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reference_doctype: (frm) => {
|
||||||
|
frm.events.set_field_options(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
set_field_options: (frm) => {
|
||||||
|
const reference_doctype = frm.doc.reference_doctype;
|
||||||
|
if (!reference_doctype) return;
|
||||||
|
|
||||||
|
frappe.model.with_doctype(reference_doctype, () => {
|
||||||
|
const map_for_options = (df) => ({
|
||||||
|
label: df.label,
|
||||||
|
value: df.fieldname,
|
||||||
|
});
|
||||||
|
const fields = frappe.meta
|
||||||
|
.get_docfields(frm.doc.reference_doctype)
|
||||||
|
.filter(frappe.model.is_value_type);
|
||||||
|
|
||||||
|
const fields_to_check = fields.map(map_for_options);
|
||||||
|
|
||||||
|
const user_fields = fields
|
||||||
|
.filter(
|
||||||
|
(df) =>
|
||||||
|
(df.fieldtype === "Link" && df.options === "User") ||
|
||||||
|
df.fieldtype === "Data"
|
||||||
|
)
|
||||||
|
.map(map_for_options)
|
||||||
|
.concat([
|
||||||
|
{ label: __("Owner"), value: "owner" },
|
||||||
|
{ label: __("Modified By"), value: "modified_by" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
frm.set_df_property("field_to_check", "options", fields_to_check);
|
||||||
|
frm.set_df_property("user_field", "options", user_fields);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const add_assign_button = (frm) => {
|
||||||
|
frm.add_custom_button(__("Assign"), function () {
|
||||||
|
frappe.call({
|
||||||
|
method: "lms.lms.doctype.lms_badge.lms_badge.assign_badge",
|
||||||
|
args: {
|
||||||
|
badge: frm.doc,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.msgprint(r.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
126
lms/lms/doctype/lms_badge/lms_badge.json
Normal file
126
lms/lms/doctype/lms_badge/lms_badge.json
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:title",
|
||||||
|
"creation": "2024-04-30 11:29:53.548647",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enabled",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"image",
|
||||||
|
"column_break_wgum",
|
||||||
|
"grant_only_once",
|
||||||
|
"event",
|
||||||
|
"reference_doctype",
|
||||||
|
"user_field",
|
||||||
|
"field_to_check",
|
||||||
|
"condition"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "title",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Title",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "image",
|
||||||
|
"fieldtype": "Attach Image",
|
||||||
|
"label": "Image",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_wgum",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_doctype",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Reference Document Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "event",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Event",
|
||||||
|
"options": "New\nValue Change\nAuto Assign",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "condition",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Condition",
|
||||||
|
"mandatory_depends_on": "eval:doc.event == \"Auto Assign\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.event == 'Value Change'",
|
||||||
|
"fieldname": "field_to_check",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Field To Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "grant_only_once",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Grant only once"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "enabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "user_field",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "User Field",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"link_doctype": "LMS Badge Assignment",
|
||||||
|
"link_fieldname": "badge"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2024-05-14 14:46:13.644382",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Badge",
|
||||||
|
"naming_rule": "By fieldname",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "title",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
97
lms/lms/doctype/lms_badge/lms_badge.py
Normal file
97
lms/lms/doctype/lms_badge/lms_badge.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Copyright (c) 2024, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
import json
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSBadge(Document):
|
||||||
|
def on_update(self):
|
||||||
|
if self.event == "Auto Assign" and self.condition:
|
||||||
|
try:
|
||||||
|
json.loads(self.condition)
|
||||||
|
except ValueError:
|
||||||
|
frappe.throw("Condition must be in valid JSON format.")
|
||||||
|
elif self.condition:
|
||||||
|
try:
|
||||||
|
compile(self.condition, "<string>", "eval")
|
||||||
|
except Exception:
|
||||||
|
frappe.throw("Condition must be valid python code.")
|
||||||
|
|
||||||
|
def apply(self, doc):
|
||||||
|
if self.rule_condition_satisfied(doc):
|
||||||
|
award(self, doc.get(self.user_field))
|
||||||
|
|
||||||
|
def rule_condition_satisfied(self, doc):
|
||||||
|
doc_before_save = doc.get_doc_before_save()
|
||||||
|
|
||||||
|
if self.event == "Manual Assignment":
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.event == "New" and doc_before_save != None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.event == "Value Change":
|
||||||
|
field_to_check = self.field_to_check
|
||||||
|
if not field_to_check:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.condition:
|
||||||
|
return eval_condition(doc, self.condition)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def award(doc, member):
|
||||||
|
if doc.grant_only_once:
|
||||||
|
if frappe.db.exists(
|
||||||
|
"LMS Badge Assignment",
|
||||||
|
{"badge": doc.name, "member": member},
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
assignment = frappe.new_doc("LMS Badge Assignment")
|
||||||
|
assignment.update(
|
||||||
|
{
|
||||||
|
"badge": doc.name,
|
||||||
|
"member": member,
|
||||||
|
"issued_on": frappe.utils.now(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assignment.save()
|
||||||
|
|
||||||
|
|
||||||
|
def eval_condition(doc, condition):
|
||||||
|
return condition and frappe.safe_eval(condition, None, {"doc": doc.as_dict()})
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def assign_badge(badge):
|
||||||
|
badge = frappe._dict(json.loads(badge))
|
||||||
|
if not badge.event == "Auto Assign":
|
||||||
|
return
|
||||||
|
|
||||||
|
fields = ["name"]
|
||||||
|
print(badge.user_field)
|
||||||
|
fields.append(badge.user_field)
|
||||||
|
list = frappe.get_all(badge.reference_doctype, filters=badge.condition, fields=fields)
|
||||||
|
print(list)
|
||||||
|
for doc in list:
|
||||||
|
award(badge, doc.get(badge.user_field))
|
||||||
|
|
||||||
|
|
||||||
|
def process_badges(doc, state):
|
||||||
|
if (
|
||||||
|
frappe.flags.in_patch
|
||||||
|
or frappe.flags.in_install
|
||||||
|
or frappe.flags.in_migrate
|
||||||
|
or frappe.flags.in_import
|
||||||
|
or frappe.flags.in_setup_wizard
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
for d in frappe.cache_manager.get_doctype_map(
|
||||||
|
"LMS Badge", doc.doctype, dict(reference_doctype=doc.doctype, enabled=1)
|
||||||
|
):
|
||||||
|
frappe.get_doc("LMS Badge", d.get("name")).apply(doc)
|
||||||
9
lms/lms/doctype/lms_badge/test_lms_badge.py
Normal file
9
lms/lms/doctype/lms_badge/test_lms_badge.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSBadge(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
lms/lms/doctype/lms_badge_assignment/__init__.py
Normal file
0
lms/lms/doctype/lms_badge_assignment/__init__.py
Normal file
14
lms/lms/doctype/lms_badge_assignment/lms_badge_assignment.js
Normal file
14
lms/lms/doctype/lms_badge_assignment/lms_badge_assignment.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright (c) 2024, Frappe and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("LMS Badge Assignment", {
|
||||||
|
refresh(frm) {
|
||||||
|
frm.set_query("member", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
ignore_user_type: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
122
lms/lms/doctype/lms_badge_assignment/lms_badge_assignment.json
Normal file
122
lms/lms/doctype/lms_badge_assignment/lms_badge_assignment.json
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2024-04-30 11:58:44.096879",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"member",
|
||||||
|
"issued_on",
|
||||||
|
"column_break_ugix",
|
||||||
|
"badge",
|
||||||
|
"badge_image",
|
||||||
|
"badge_description"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "member",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Member",
|
||||||
|
"options": "User",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "badge",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Badge",
|
||||||
|
"options": "LMS Badge",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "issued_on",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Issued On",
|
||||||
|
"options": "Today",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "badge.image",
|
||||||
|
"fieldname": "badge_image",
|
||||||
|
"fieldtype": "Attach",
|
||||||
|
"label": "Badge Image",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ugix",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "badge.description",
|
||||||
|
"fieldname": "badge_description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Badge Description",
|
||||||
|
"read_only": 1,
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-05-13 20:16:00.191517",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "LMS Badge Assignment",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Moderator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"if_owner": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"show_title_field_in_link": 1,
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
|
"title_field": "member"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, Frappe and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class LMSBadgeAssignment(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2024, Frappe and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestLMSBadgeAssignment(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "format: CLS-{#####}",
|
"autoname": "format: CLS-{#####}",
|
||||||
"creation": "2022-11-09 16:14:05.876933",
|
"creation": "2022-11-09 16:14:05.876933",
|
||||||
@@ -304,7 +305,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-04-17 10:35:21.957961",
|
"modified": "2024-05-14 14:47:48.839162",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch",
|
"name": "LMS Batch",
|
||||||
@@ -352,5 +353,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "title"
|
"title_field": "title",
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"creation": "2021-08-16 15:47:19.494055",
|
"creation": "2021-08-16 15:47:19.494055",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-04-09 13:42:18.350028",
|
"modified": "2024-05-14 14:48:31.650107",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Certificate",
|
"name": "LMS Certificate",
|
||||||
@@ -116,6 +117,15 @@
|
|||||||
"role": "Moderator",
|
"role": "Moderator",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "LMS Student",
|
||||||
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -273,7 +273,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2024-05-09 14:45:03.041209",
|
"modified": "2024-05-08 15:11:07.833094",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
# Copyright (c) 2021, FOSS United and contributors
|
# Copyright (c) 2021, FOSS United and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from lms.lms.utils import get_course_progress
|
||||||
|
|
||||||
|
|
||||||
class LMSCourseProgress(Document):
|
class LMSCourseProgress(Document):
|
||||||
pass
|
def after_delete(self):
|
||||||
|
progress = get_course_progress(self.course, self.member)
|
||||||
|
membership = frappe.db.get_value(
|
||||||
|
"LMS Enrollment",
|
||||||
|
{
|
||||||
|
"member": self.member,
|
||||||
|
"course": self.course,
|
||||||
|
},
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"creation": "2022-02-07 12:01:40.929633",
|
"creation": "2022-02-07 12:01:40.929633",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"course",
|
"course",
|
||||||
"member_type",
|
"progress",
|
||||||
"payment",
|
"payment",
|
||||||
|
"current_lesson",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
@@ -17,8 +19,7 @@
|
|||||||
"subgroup",
|
"subgroup",
|
||||||
"batch_old",
|
"batch_old",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"current_lesson",
|
"member_type",
|
||||||
"progress",
|
|
||||||
"role"
|
"role"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -113,7 +114,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "payment",
|
"fieldname": "payment",
|
||||||
@@ -124,7 +126,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-18 17:32:30.182301",
|
"modified": "2024-05-14 14:50:08.405033",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Enrollment",
|
"name": "LMS Enrollment",
|
||||||
@@ -173,5 +175,6 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "member_name"
|
"title_field": "member_name",
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,7 @@ def quiz_summary(quiz, results):
|
|||||||
"passing_percentage": quiz_details.passing_percentage,
|
"passing_percentage": quiz_details.passing_percentage,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
submission.save(ignore_permissions=True)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
percentage >= quiz_details.passing_percentage
|
percentage >= quiz_details.passing_percentage
|
||||||
@@ -110,8 +111,8 @@ def quiz_summary(quiz, results):
|
|||||||
and quiz_details.course
|
and quiz_details.course
|
||||||
):
|
):
|
||||||
save_progress(quiz_details.lesson, quiz_details.course)
|
save_progress(quiz_details.lesson, quiz_details.course)
|
||||||
|
elif not quiz_details.passing_percentage:
|
||||||
submission.save(ignore_permissions=True)
|
save_progress(quiz_details.lesson, quiz_details.course)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"score": score,
|
"score": score,
|
||||||
|
|||||||
@@ -115,27 +115,27 @@ def get_chapters(course):
|
|||||||
return chapters
|
return chapters
|
||||||
|
|
||||||
|
|
||||||
def get_lessons(course, chapter=None, get_details=True):
|
def get_lessons(course, chapter=None, get_details=True, progress=False):
|
||||||
"""If chapter is passed, returns lessons of only that chapter.
|
"""If chapter is passed, returns lessons of only that chapter.
|
||||||
Else returns lessons of all chapters of the course"""
|
Else returns lessons of all chapters of the course"""
|
||||||
lessons = []
|
lessons = []
|
||||||
lesson_count = 0
|
lesson_count = 0
|
||||||
if chapter:
|
if chapter:
|
||||||
if get_details:
|
if get_details:
|
||||||
return get_lesson_details(chapter)
|
return get_lesson_details(chapter, progress=progress)
|
||||||
else:
|
else:
|
||||||
return frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
return frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
||||||
|
|
||||||
for chapter in get_chapters(course):
|
for chapter in get_chapters(course):
|
||||||
if get_details:
|
if get_details:
|
||||||
lessons += get_lesson_details(chapter)
|
lessons += get_lesson_details(chapter, progress=progress)
|
||||||
else:
|
else:
|
||||||
lesson_count += frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
lesson_count += frappe.db.count("Lesson Reference", {"parent": chapter.name})
|
||||||
|
|
||||||
return lessons if get_details else lesson_count
|
return lessons if get_details else lesson_count
|
||||||
|
|
||||||
|
|
||||||
def get_lesson_details(chapter):
|
def get_lesson_details(chapter, progress=False):
|
||||||
lessons = []
|
lessons = []
|
||||||
lesson_list = frappe.get_all(
|
lesson_list = frappe.get_all(
|
||||||
"Lesson Reference", {"parent": chapter.name}, ["lesson", "idx"], order_by="idx"
|
"Lesson Reference", {"parent": chapter.name}, ["lesson", "idx"], order_by="idx"
|
||||||
@@ -161,6 +161,10 @@ def get_lesson_details(chapter):
|
|||||||
)
|
)
|
||||||
lesson_details.number = f"{chapter.idx}.{row.idx}"
|
lesson_details.number = f"{chapter.idx}.{row.idx}"
|
||||||
lesson_details.icon = get_lesson_icon(lesson_details.body)
|
lesson_details.icon = get_lesson_icon(lesson_details.body)
|
||||||
|
|
||||||
|
if progress:
|
||||||
|
lesson_details.is_complete = get_progress(lesson_details.course, lesson_details.name)
|
||||||
|
|
||||||
lessons.append(lesson_details)
|
lessons.append(lesson_details)
|
||||||
return lessons
|
return lessons
|
||||||
|
|
||||||
@@ -306,7 +310,7 @@ def get_progress(course, lesson, member=None):
|
|||||||
if not member:
|
if not member:
|
||||||
member = frappe.session.user
|
member = frappe.session.user
|
||||||
|
|
||||||
return frappe.db.get_value(
|
return frappe.db.exists(
|
||||||
"LMS Course Progress",
|
"LMS Course Progress",
|
||||||
{"course": course, "member": member, "lesson": lesson},
|
{"course": course, "member": member, "lesson": lesson},
|
||||||
["status"],
|
["status"],
|
||||||
@@ -379,7 +383,7 @@ def get_course_progress(course, member=None):
|
|||||||
return 0
|
return 0
|
||||||
completed_lessons = frappe.db.count(
|
completed_lessons = frappe.db.count(
|
||||||
"LMS Course Progress",
|
"LMS Course Progress",
|
||||||
{"course": course, "owner": member or frappe.session.user, "status": "Complete"},
|
{"course": course, "member": member or frappe.session.user, "status": "Complete"},
|
||||||
)
|
)
|
||||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||||
return flt(((completed_lessons / lesson_count) * 100), precision)
|
return flt(((completed_lessons / lesson_count) * 100), precision)
|
||||||
@@ -1300,7 +1304,7 @@ def get_categorized_courses(courses):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_course_outline(course):
|
def get_course_outline(course, progress=False):
|
||||||
"""Returns the course outline."""
|
"""Returns the course outline."""
|
||||||
outline = []
|
outline = []
|
||||||
chapters = frappe.get_all(
|
chapters = frappe.get_all(
|
||||||
@@ -1314,7 +1318,7 @@ def get_course_outline(course):
|
|||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
chapter_details["idx"] = chapter.idx
|
chapter_details["idx"] = chapter.idx
|
||||||
chapter_details.lessons = get_lessons(course, chapter_details)
|
chapter_details.lessons = get_lessons(course, chapter_details, progress=progress)
|
||||||
outline.append(chapter_details)
|
outline.append(chapter_details)
|
||||||
return outline
|
return outline
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
<meta name="twitter:title" content="{{ meta.title }}" />
|
<meta name="twitter:title" content="{{ meta.title }}" />
|
||||||
<meta name="twitter:image" content="{{ meta.image }}" />
|
<meta name="twitter:image" content="{{ meta.image }}" />
|
||||||
<meta name="twitter:description" content="{{ meta.description }}" />
|
<meta name="twitter:description" content="{{ meta.description }}" />
|
||||||
<script type="module" crossorigin src="/assets/lms/frontend/assets/index-Ddsp6_Rz.js"></script>
|
<script type="module" crossorigin src="/assets/lms/frontend/assets/index-THAMiyCA.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-BI4McHL7.js">
|
<link rel="modulepreload" crossorigin href="/assets/lms/frontend/assets/frappe-ui-CgFK8870.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css">
|
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/frappe-ui-DzKBfka9.css">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-B8lu6r6G.css">
|
<link rel="stylesheet" crossorigin href="/assets/lms/frontend/assets/index-C1pDkvO9.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
import frappe
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
|
||||||
def get_add_on_details(plan: str) -> dict[str, int]:
|
|
||||||
"""
|
|
||||||
Returns the number of courses and course members to be billed under add-ons for SAAS subscription
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {"courses": get_add_on_courses(plan), "members": get_add_on_members(plan)}
|
|
||||||
|
|
||||||
|
|
||||||
def get_published_courses() -> int:
|
|
||||||
return frappe.db.count("LMS Course", {"published": 1})
|
|
||||||
|
|
||||||
|
|
||||||
def get_add_on_courses(plan: str) -> int:
|
|
||||||
COURSE_LIMITS = {"Lite": 5, "Pro": 20}
|
|
||||||
add_on_courses = 0
|
|
||||||
courses_included_in_plans = COURSE_LIMITS.get(plan)
|
|
||||||
|
|
||||||
if courses_included_in_plans:
|
|
||||||
published_courses = get_published_courses()
|
|
||||||
add_on_courses = (
|
|
||||||
published_courses - courses_included_in_plans
|
|
||||||
if published_courses > courses_included_in_plans
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
return add_on_courses
|
|
||||||
|
|
||||||
|
|
||||||
def get_add_on_members(plan: str) -> int:
|
|
||||||
MEMBER_LIMITS = {"Lite": 500, "Pro": 1000}
|
|
||||||
add_on_members = 0
|
|
||||||
members_included_in_plans = MEMBER_LIMITS.get(plan)
|
|
||||||
|
|
||||||
if members_included_in_plans:
|
|
||||||
active_members = get_members()
|
|
||||||
add_on_members = (
|
|
||||||
active_members - members_included_in_plans
|
|
||||||
if active_members > members_included_in_plans
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
return add_on_members
|
|
||||||
|
|
||||||
|
|
||||||
def get_members() -> int:
|
|
||||||
return frappe.db.count("LMS Enrollment")
|
|
||||||
@@ -30,7 +30,6 @@ def make_unsplash_request(path):
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
url = f"{base_url}{path}"
|
url = f"{base_url}{path}"
|
||||||
print(url)
|
|
||||||
res = requests.get(
|
res = requests.get(
|
||||||
url,
|
url,
|
||||||
headers={
|
headers={
|
||||||
|
|||||||
Reference in New Issue
Block a user