chore: resolved conflicts
This commit is contained in:
@@ -188,6 +188,10 @@ website_route_rules = [
|
|||||||
"from_route": "/quiz-submission/<quiz>/<submission>",
|
"from_route": "/quiz-submission/<quiz>/<submission>",
|
||||||
"to_route": "quiz_submission/quiz_submission",
|
"to_route": "quiz_submission/quiz_submission",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"from_route": "/billing/<course>",
|
||||||
|
"to_route": "billing/billing",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
website_redirects = [
|
website_redirects = [
|
||||||
|
|||||||
@@ -12,6 +12,14 @@
|
|||||||
"member",
|
"member",
|
||||||
"member_name",
|
"member_name",
|
||||||
"member_username",
|
"member_username",
|
||||||
|
"billing_information_section",
|
||||||
|
"address",
|
||||||
|
"amount",
|
||||||
|
"currency",
|
||||||
|
"column_break_rvzn",
|
||||||
|
"order_id",
|
||||||
|
"payment_id",
|
||||||
|
"payment_received",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"cohort",
|
"cohort",
|
||||||
"subgroup",
|
"subgroup",
|
||||||
@@ -112,11 +120,54 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "section_break_8",
|
"fieldname": "section_break_8",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_information_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Billing Information"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "address",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Address",
|
||||||
|
"options": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "payment_received",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Payment Received"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_rvzn",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "order_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Order ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Payment ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-10 12:38:17.839526",
|
"modified": "2023-08-17 13:52:49.450499",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch Membership",
|
"name": "LMS Batch Membership",
|
||||||
@@ -141,4 +192,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "member_name"
|
"title_field": "member_name"
|
||||||
}
|
}
|
||||||
@@ -11,14 +11,6 @@ frappe.ui.form.on("LMS Course", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("instructor", "instructors", function () {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
ignore_user_type: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("course", "related_courses", function () {
|
frm.set_query("course", "related_courses", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -29,5 +21,12 @@ frappe.ui.form.on("LMS Course", {
|
|||||||
},
|
},
|
||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
frm.add_web_link(`/courses/${frm.doc.name}`, "See on Website");
|
frm.add_web_link(`/courses/${frm.doc.name}`, "See on Website");
|
||||||
|
|
||||||
|
if (!frm.doc.currency)
|
||||||
|
frappe.db
|
||||||
|
.get_single_value("LMS Settings", "default_currency")
|
||||||
|
.then((value) => {
|
||||||
|
frm.set_value("currency", value);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,19 +32,18 @@
|
|||||||
"description",
|
"description",
|
||||||
"chapters",
|
"chapters",
|
||||||
"related_courses",
|
"related_courses",
|
||||||
|
"pricing_section",
|
||||||
|
"paid_course",
|
||||||
|
"currency",
|
||||||
|
"course_price",
|
||||||
"certification_section",
|
"certification_section",
|
||||||
"enable_certification",
|
"enable_certification",
|
||||||
"expiry",
|
"expiry",
|
||||||
"section_break_23",
|
"max_attempts",
|
||||||
|
"column_break_rxww",
|
||||||
"grant_certificate_after",
|
"grant_certificate_after",
|
||||||
"evaluator",
|
"evaluator",
|
||||||
"column_break_26",
|
"duration"
|
||||||
"max_attempts",
|
|
||||||
"duration",
|
|
||||||
"pricing_section",
|
|
||||||
"paid_certificate",
|
|
||||||
"currency",
|
|
||||||
"price_certificate"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -170,13 +169,6 @@
|
|||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"depends_on": "enable_certification",
|
|
||||||
"fieldname": "paid_certificate",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Paid Certificate"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "enable_certification",
|
"depends_on": "enable_certification",
|
||||||
"fieldname": "grant_certificate_after",
|
"fieldname": "grant_certificate_after",
|
||||||
@@ -193,24 +185,16 @@
|
|||||||
"options": "Course Evaluator"
|
"options": "Course Evaluator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.grant_certificate_after == \"Evaluation\"",
|
|
||||||
"fieldname": "pricing_section",
|
"fieldname": "pricing_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Pricing"
|
"label": "Pricing"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "paid_certificate",
|
"depends_on": "paid_course",
|
||||||
"fieldname": "price_certificate",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Certificate Price",
|
|
||||||
"mandatory_depends_on": "paid_certificate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"depends_on": "paid_certificate",
|
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Currency",
|
"label": "Currency",
|
||||||
"mandatory_depends_on": "paid_certificate",
|
"mandatory_depends_on": "paid_course",
|
||||||
"options": "Currency"
|
"options": "Currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -228,11 +212,21 @@
|
|||||||
"options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12"
|
"options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_23",
|
"default": "0",
|
||||||
"fieldtype": "Section Break"
|
"fieldname": "paid_course",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Paid Course"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_26",
|
"depends_on": "paid_course",
|
||||||
|
"fieldname": "course_price",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Course Price",
|
||||||
|
"option": "currency",
|
||||||
|
"mandatory_depends_on": "paid_course"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_rxww",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -260,39 +254,12 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2023-05-11 17:08:19.763405",
|
"modified": "2023-08-02 12:07:26.354110",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [],
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"delete": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "System Manager",
|
|
||||||
"select": 1,
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"create": 1,
|
|
||||||
"email": 1,
|
|
||||||
"export": 1,
|
|
||||||
"if_owner": 1,
|
|
||||||
"print": 1,
|
|
||||||
"read": 1,
|
|
||||||
"report": 1,
|
|
||||||
"role": "All",
|
|
||||||
"select": 1,
|
|
||||||
"share": 1,
|
|
||||||
"write": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"search_fields": "title",
|
"search_fields": "title",
|
||||||
"show_title_field_in_link": 1,
|
"show_title_field_in_link": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import json
|
|||||||
import random
|
import random
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint, validate_phone_number
|
||||||
from frappe.utils.telemetry import capture
|
from frappe.utils.telemetry import capture
|
||||||
from lms.lms.utils import get_chapters, can_create_courses
|
from lms.lms.utils import get_chapters, can_create_courses
|
||||||
from ...utils import generate_slug, validate_image
|
from ...utils import generate_slug, validate_image
|
||||||
|
from frappe import _
|
||||||
|
import razorpay
|
||||||
|
|
||||||
|
|
||||||
class LMSCourse(Document):
|
class LMSCourse(Document):
|
||||||
@@ -211,6 +213,9 @@ def save_course(
|
|||||||
published,
|
published,
|
||||||
upcoming,
|
upcoming,
|
||||||
image=None,
|
image=None,
|
||||||
|
paid_course=False,
|
||||||
|
course_price=None,
|
||||||
|
currency=None,
|
||||||
):
|
):
|
||||||
if not can_create_courses():
|
if not can_create_courses():
|
||||||
return
|
return
|
||||||
@@ -230,6 +235,9 @@ def save_course(
|
|||||||
"tags": tags,
|
"tags": tags,
|
||||||
"published": cint(published),
|
"published": cint(published),
|
||||||
"upcoming": cint(upcoming),
|
"upcoming": cint(upcoming),
|
||||||
|
"paid_course": cint(paid_course),
|
||||||
|
"course_price": course_price,
|
||||||
|
"currency": currency,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
@@ -354,3 +362,115 @@ def reorder_chapter(chapter_array):
|
|||||||
"idx": chapter_array.index(chap) + 1,
|
"idx": chapter_array.index(chap) + 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_payment_options(course, phone):
|
||||||
|
validate_phone_number(phone, True)
|
||||||
|
course_details = frappe.db.get_value(
|
||||||
|
"LMS Course", course, ["name", "title", "currency", "course_price"], as_dict=True
|
||||||
|
)
|
||||||
|
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
||||||
|
client = get_client()
|
||||||
|
order = create_order(client, course_details)
|
||||||
|
|
||||||
|
options = {
|
||||||
|
"key_id": razorpay_key,
|
||||||
|
"name": frappe.db.get_single_value("Website Settings", "app_name"),
|
||||||
|
"description": _("Payment for {0} course").format(course_details["title"]),
|
||||||
|
"order_id": order["id"],
|
||||||
|
"amount": order["amount"] * 100,
|
||||||
|
"currency": order["currency"],
|
||||||
|
"prefill": {
|
||||||
|
"name": frappe.db.get_value("User", frappe.session.user, "full_name"),
|
||||||
|
"email": frappe.session.user,
|
||||||
|
"contact": phone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
def save_address(address):
|
||||||
|
address = json.loads(address)
|
||||||
|
address.update(
|
||||||
|
{
|
||||||
|
"address_title": frappe.db.get_value("User", frappe.session.user, "full_name"),
|
||||||
|
"address_type": "Billing",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"email_id": frappe.session.user,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
doc = frappe.new_doc("Address")
|
||||||
|
doc.update(address)
|
||||||
|
doc.save(ignore_permissions=True)
|
||||||
|
return doc.name
|
||||||
|
|
||||||
|
|
||||||
|
def get_client():
|
||||||
|
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
||||||
|
razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret")
|
||||||
|
|
||||||
|
if not razorpay_key and not razorpay_secret:
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"There is a problem with the payment gateway. Please contact the Administrator to proceed."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return razorpay.Client(auth=(razorpay_key, razorpay_secret))
|
||||||
|
|
||||||
|
|
||||||
|
def create_order(client, course_details):
|
||||||
|
try:
|
||||||
|
return client.order.create(
|
||||||
|
{
|
||||||
|
"amount": course_details.course_price * 100,
|
||||||
|
"currency": course_details.currency,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.throw(
|
||||||
|
_("Error during payment: {0}. Please contact the Administrator.").format(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def verify_payment(response, course, address, order_id):
|
||||||
|
response = json.loads(response)
|
||||||
|
client = get_client()
|
||||||
|
client.utility.verify_payment_signature(
|
||||||
|
{
|
||||||
|
"razorpay_order_id": order_id,
|
||||||
|
"razorpay_payment_id": response["razorpay_payment_id"],
|
||||||
|
"razorpay_signature": response["razorpay_signature"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return create_membership(address, response, course, client)
|
||||||
|
|
||||||
|
|
||||||
|
def create_membership(address, response, course, client):
|
||||||
|
try:
|
||||||
|
address_name = save_address(address)
|
||||||
|
membership = frappe.new_doc("LMS Batch Membership")
|
||||||
|
payment = client.payment.fetch(response["razorpay_payment_id"])
|
||||||
|
|
||||||
|
membership.update(
|
||||||
|
{
|
||||||
|
"member": frappe.session.user,
|
||||||
|
"course": course,
|
||||||
|
"address": address_name,
|
||||||
|
"payment_received": 1,
|
||||||
|
"order_id": response["razorpay_order_id"],
|
||||||
|
"payment_id": response["razorpay_payment_id"],
|
||||||
|
"amount": payment["amount"] / 100,
|
||||||
|
"currency": payment["currency"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
membership.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
return f"/courses/{course}/learn/1.1"
|
||||||
|
except Exception as e:
|
||||||
|
frappe.throw(
|
||||||
|
_("Error during payment: {0}. Please contact the Administrator.").format(e)
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,16 +6,23 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"default_home",
|
"default_home",
|
||||||
"send_calendar_invite_for_evaluations",
|
|
||||||
"allow_student_progress",
|
|
||||||
"column_break_zdel",
|
|
||||||
"is_onboarding_complete",
|
|
||||||
"force_profile_completion",
|
"force_profile_completion",
|
||||||
"section_break_szgq",
|
"is_onboarding_complete",
|
||||||
"search_placeholder",
|
"column_break_zdel",
|
||||||
"portal_course_creation",
|
|
||||||
"column_break_2",
|
|
||||||
"livecode_url",
|
"livecode_url",
|
||||||
|
"course_settings_section",
|
||||||
|
"search_placeholder",
|
||||||
|
"column_break_iqxy",
|
||||||
|
"portal_course_creation",
|
||||||
|
"section_break_szgq",
|
||||||
|
"send_calendar_invite_for_evaluations",
|
||||||
|
"column_break_2",
|
||||||
|
"allow_student_progress",
|
||||||
|
"payment_section",
|
||||||
|
"razorpay_key",
|
||||||
|
"default_currency",
|
||||||
|
"column_break_cfcv",
|
||||||
|
"razorpay_secret",
|
||||||
"signup_settings_tab",
|
"signup_settings_tab",
|
||||||
"signup_settings_section",
|
"signup_settings_section",
|
||||||
"terms_of_use",
|
"terms_of_use",
|
||||||
@@ -37,6 +44,7 @@
|
|||||||
"default": "https://livecode.dev.fossunited.org",
|
"default": "https://livecode.dev.fossunited.org",
|
||||||
"fieldname": "livecode_url",
|
"fieldname": "livecode_url",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
"label": "LiveCode URL"
|
"label": "LiveCode URL"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -163,7 +171,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_szgq",
|
"fieldname": "section_break_szgq",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Class Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "signup_settings_tab",
|
"fieldname": "signup_settings_tab",
|
||||||
@@ -180,12 +189,46 @@
|
|||||||
"fieldname": "allow_student_progress",
|
"fieldname": "allow_student_progress",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow students to see each others progress in class"
|
"label": "Allow students to see each others progress in class"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Payment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "default_currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Default Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_cfcv",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "course_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Course Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_iqxy",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "razorpay_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Razorpay Key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "razorpay_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "Razorpay Secret"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-17 12:54:44.706101",
|
"modified": "2023-08-02 18:59:01.267732",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Settings",
|
"name": "LMS Settings",
|
||||||
|
|||||||
@@ -475,7 +475,7 @@ def get_evaluation_details(course, member=None):
|
|||||||
def format_amount(amount, currency):
|
def format_amount(amount, currency):
|
||||||
amount_reduced = amount / 1000
|
amount_reduced = amount / 1000
|
||||||
if amount_reduced < 1:
|
if amount_reduced < 1:
|
||||||
return amount
|
return fmt_money(amount, 0, currency)
|
||||||
precision = 0 if amount % 1000 == 0 else 1
|
precision = 0 if amount % 1000 == 0 else 1
|
||||||
return _("{0}k").format(fmt_money(amount_reduced, precision, currency))
|
return _("{0}k").format(fmt_money(amount_reduced, precision, currency))
|
||||||
|
|
||||||
@@ -559,7 +559,9 @@ def get_courses_under_review():
|
|||||||
"title",
|
"title",
|
||||||
"short_introduction",
|
"short_introduction",
|
||||||
"image",
|
"image",
|
||||||
"enable_certification",
|
"paid_course",
|
||||||
|
"course_price",
|
||||||
|
"currency",
|
||||||
"status",
|
"status",
|
||||||
"published",
|
"published",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -58,19 +58,12 @@
|
|||||||
{{ frappe.utils.flt(course.avg_rating, frappe.get_system_settings("float_precision") or 3) }}
|
{{ frappe.utils.flt(course.avg_rating, frappe.get_system_settings("float_precision") or 3) }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if course.paid_certificate %}
|
|
||||||
<div class="vertically-center">
|
|
||||||
<svg class="icon icon-md">
|
|
||||||
<use href="#icon-badge"></use>
|
|
||||||
</svg>
|
|
||||||
<span class="certificate-price" data-price="{{ course.price_certificate }}">
|
|
||||||
{{ format_amount(course.price_certificate, course.currency) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="course-card-title">{{ course.title }}</div>
|
|
||||||
|
<div class="course-card-title">
|
||||||
|
{{ course.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="short-introduction">
|
<div class="short-introduction">
|
||||||
{{ course.short_introduction }}
|
{{ course.short_introduction }}
|
||||||
</div>
|
</div>
|
||||||
@@ -86,7 +79,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="course-card-footer">
|
<div class="course-card-footer">
|
||||||
<span class="">
|
|
||||||
|
<div class="course-card-instructors">
|
||||||
{% set instructors = get_instructors(course.name) %}
|
{% set instructors = get_instructors(course.name) %}
|
||||||
{% set ins_len = instructors | length %}
|
{% set ins_len = instructors | length %}
|
||||||
{% for instructor in instructors %}
|
{% for instructor in instructors %}
|
||||||
@@ -111,7 +105,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
|
<div class="course-price">
|
||||||
|
{% if course.paid_course %}
|
||||||
|
{{ frappe.utils.fmt_money(course.course_price, 0, course.currency) }}
|
||||||
|
{% else %}
|
||||||
|
{{ _("Free") }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if read_only %}
|
{% if read_only %}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
{{ _("Notify me when available") }}
|
{{ _("Notify me when available") }}
|
||||||
</button>
|
</button>
|
||||||
{% elif show_start_learing_cta(course, membership) %}
|
{% elif show_start_learing_cta(course, membership) %}
|
||||||
<button class="btn btn-primary btn-sm join-batch pull-right" data-course="{{ course.name | urlencode}}">
|
<button class="btn btn-primary btn-sm enroll-in-course pull-right" data-course="{{ course.name | urlencode}}">
|
||||||
{{ _("Start Learning") }}
|
{{ _("Start Learning") }}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ def get_enrolled_courses():
|
|||||||
"short_introduction",
|
"short_introduction",
|
||||||
"image",
|
"image",
|
||||||
"enable_certification",
|
"enable_certification",
|
||||||
"paid_certificate",
|
"paid_course",
|
||||||
"price_certificate",
|
"course_price",
|
||||||
"currency",
|
"currency",
|
||||||
"published",
|
"published",
|
||||||
"creation",
|
"creation",
|
||||||
@@ -153,7 +153,9 @@ def get_authored_courses(member=None, only_published=True):
|
|||||||
"title",
|
"title",
|
||||||
"short_introduction",
|
"short_introduction",
|
||||||
"image",
|
"image",
|
||||||
"enable_certification",
|
"paid_course",
|
||||||
|
"course_price",
|
||||||
|
"currency",
|
||||||
"status",
|
"status",
|
||||||
"published",
|
"published",
|
||||||
"creation",
|
"creation",
|
||||||
|
|||||||
@@ -60,4 +60,5 @@ lms.patches.v1_0.create_class_evaluator_role
|
|||||||
execute:frappe.permissions.reset_perms("LMS Class")
|
execute:frappe.permissions.reset_perms("LMS Class")
|
||||||
execute:frappe.permissions.reset_perms("Course Evaluator")
|
execute:frappe.permissions.reset_perms("Course Evaluator")
|
||||||
execute:frappe.permissions.reset_perms("LMS Certificate Request")
|
execute:frappe.permissions.reset_perms("LMS Certificate Request")
|
||||||
execute:frappe.permissions.reset_perms("LMS Certificate Evaluation")
|
execute:frappe.permissions.reset_perms("LMS Certificate Evaluation")
|
||||||
|
lms.patches.v1_0.paid_certificate_to_paid_course
|
||||||
20
lms/patches/v1_0/paid_certificate_to_paid_course.py
Normal file
20
lms/patches/v1_0/paid_certificate_to_paid_course.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
courses = frappe.get_all(
|
||||||
|
"LMS Course",
|
||||||
|
{"paid_certificate": ["is", "set"]},
|
||||||
|
["name", "price_certificate", "currency"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for course in courses:
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Course",
|
||||||
|
course.name,
|
||||||
|
{
|
||||||
|
"paid_course": 1,
|
||||||
|
"course_price": course.price_certificate,
|
||||||
|
"currency": course.currency,
|
||||||
|
},
|
||||||
|
)
|
||||||
@@ -415,11 +415,13 @@ input[type=checkbox] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.course-card-footer {
|
.course-card-footer {
|
||||||
margin-top: auto;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-card-footer .avatar-group {
|
.course-price {
|
||||||
display: inherit;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-course-link {
|
.view-course-link {
|
||||||
@@ -552,6 +554,11 @@ input[type=checkbox] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.course-card-instructors {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.course-card-wide-content {
|
.course-card-wide-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -2285,6 +2292,13 @@ select {
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.course-filter {
|
.form-section .section-head {
|
||||||
|
margin-bottom: var(--margin-sm);
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--heading-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-column:first-child {
|
||||||
|
padding-right: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,8 @@ frappe.ready(() => {
|
|||||||
setup_file_size();
|
setup_file_size();
|
||||||
pin_header();
|
pin_header();
|
||||||
|
|
||||||
$(".join-batch").click((e) => {
|
$(".enroll-in-course").click((e) => {
|
||||||
join_course(e);
|
enroll_in_course(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".notify-me").click((e) => {
|
$(".notify-me").click((e) => {
|
||||||
@@ -72,7 +72,7 @@ const file_size = (value) => {
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const join_course = (e) => {
|
const enroll_in_course = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let course = $(e.currentTarget).attr("data-course");
|
let course = $(e.currentTarget).attr("data-course");
|
||||||
if (frappe.session.user == "Guest") {
|
if (frappe.session.user == "Guest") {
|
||||||
@@ -205,7 +205,7 @@ const expand_the_first_chapter = () => {
|
|||||||
|
|
||||||
const expand_the_active_chapter = () => {
|
const expand_the_active_chapter = () => {
|
||||||
let selector = $(".course-home-headings.title");
|
let selector = $(".course-home-headings.title");
|
||||||
console.log(selector);
|
|
||||||
if (selector.length && $(".course-details-page").length) {
|
if (selector.length && $(".course-details-page").length) {
|
||||||
expand_for_course_details(selector);
|
expand_for_course_details(selector);
|
||||||
} else if ($(".active-lesson").length) {
|
} else if ($(".active-lesson").length) {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<span class="btn btn-secondary btn-sm review-link">
|
<span class="btn btn-secondary btn-sm review-link">
|
||||||
{{ _("Write a review") }}
|
{{ _("Write a review") }}
|
||||||
</span>
|
</span>
|
||||||
{% elif not is_instructor(course.name) and frappe.session.user == "Guest" %}
|
{% elif not is_instructor and frappe.session.user == "Guest" %}
|
||||||
<a class="btn btn-secondary btn-sm" href="/login?redirect-to=/courses/{{ course.name }}">
|
<a class="btn btn-secondary btn-sm" href="/login?redirect-to=/courses/{{ course.name }}">
|
||||||
{{ _("Write a review") }}
|
{{ _("Write a review") }}
|
||||||
</a>
|
</a>
|
||||||
{% elif show_start_learing_cta(course, membership) %}
|
{% elif show_start_learing_cta(course, membership) %}
|
||||||
<div class="btn btn-secondary btn-sm join-batch" data-course="{{ course.name | urlencode }}">
|
<div class="btn btn-secondary btn-sm enroll-in-course" data-course="{{ course.name | urlencode }}">
|
||||||
{{ _("Start Learning") }}
|
{{ _("Start Learning") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
{{ render_html(lesson) }}
|
{{ render_html(lesson) }}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set course_link = "<a class='join-batch' data-course=" + course.name | urlencode + " href=''>" + _('here') + "</a>" %}
|
{% set course_link = "<a class='enroll-in-course' data-course=" + course.name | urlencode + " href=''>" + _('here') + "</a>" %}
|
||||||
<div class="alert alert-info medium mb-0">
|
<div class="alert alert-info medium mb-0">
|
||||||
{{ _("There is no preview available for this lesson.
|
{{ _("There is no preview available for this lesson.
|
||||||
Please join the course to access it.
|
Please join the course to access it.
|
||||||
|
|||||||
0
lms/www/billing/__init__.py
Normal file
0
lms/www/billing/__init__.py
Normal file
68
lms/www/billing/billing.html
Normal file
68
lms/www/billing/billing.html
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{% extends "lms/templates/lms_base.html" %}
|
||||||
|
{% block title %}
|
||||||
|
{{ course.title if course.title else _("New Course") }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block page_content %}
|
||||||
|
<div class="common-page-style">
|
||||||
|
<div class="container form-width common-card-style column-card px-0 h-0 mt-8">
|
||||||
|
{{ Header() }}
|
||||||
|
{{ CourseDetails() }}
|
||||||
|
{{ BillingDetails() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% macro Header() %}
|
||||||
|
<div class="px-4 pb-2">
|
||||||
|
<div class="page-title">
|
||||||
|
{{ _("Order Details") }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ _("Enter the billing information and complete the payment to purchase this course.") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro CourseDetails() %}
|
||||||
|
<div class="px-4 pt-5 border-top">
|
||||||
|
<div class="">
|
||||||
|
<div class="flex mb-2">
|
||||||
|
<div class="field-label">
|
||||||
|
{{ _("Course Name: ") }} {{ course.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<div class="field-label">
|
||||||
|
{{ _("Total Price: ") }} {{ frappe.utils.fmt_money(course.course_price, 2, course.currency) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro BillingDetails() %}
|
||||||
|
<div class="mt-8 px-4">
|
||||||
|
<div class="bold-heading mb-4">
|
||||||
|
{{ _("Billing Details") }}
|
||||||
|
</div>
|
||||||
|
<div id="billing-form"></div>
|
||||||
|
<button class="btn btn-primary btn-md btn-pay" data-course="{{ course.name | urlencode }}">
|
||||||
|
{{ "Proceed to Payment" }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{%- block script %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
|
||||||
|
<script>
|
||||||
|
frappe.boot.user = {
|
||||||
|
"can_create": [],
|
||||||
|
"can_select": ["Country"],
|
||||||
|
"can_read": ["Country"]
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
111
lms/www/billing/billing.js
Normal file
111
lms/www/billing/billing.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
frappe.ready(() => {
|
||||||
|
if ($("#billing-form").length) {
|
||||||
|
setup_billing();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".btn-pay").click((e) => {
|
||||||
|
generate_payment_link(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const setup_billing = () => {
|
||||||
|
this.billing = new frappe.ui.FieldGroup({
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("Address Line 1"),
|
||||||
|
fieldname: "address_line1",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("Address Line 2"),
|
||||||
|
fieldname: "address_line2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("City/Town"),
|
||||||
|
fieldname: "city",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("State/Province"),
|
||||||
|
fieldname: "state",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Column Break",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Link",
|
||||||
|
label: __("Country"),
|
||||||
|
fieldname: "country",
|
||||||
|
options: "Country",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("Postal Code"),
|
||||||
|
fieldname: "pincode",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("Phone Number"),
|
||||||
|
fieldname: "phone",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
body: $("#billing-form").get(0),
|
||||||
|
});
|
||||||
|
this.billing.make();
|
||||||
|
$("#billing-form .form-section:last").removeClass("empty-section");
|
||||||
|
$("#billing-form .frappe-control").removeClass("hide-control");
|
||||||
|
$("#billing-form .form-column").addClass("p-0");
|
||||||
|
};
|
||||||
|
|
||||||
|
const generate_payment_link = (e) => {
|
||||||
|
address = this.billing.get_values();
|
||||||
|
let course = decodeURIComponent($(e.currentTarget).attr("data-course"));
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "lms.lms.doctype.lms_course.lms_course.get_payment_options",
|
||||||
|
args: {
|
||||||
|
course: course,
|
||||||
|
phone: address.phone,
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
data.message.handler = (response) => {
|
||||||
|
handle_success(
|
||||||
|
response,
|
||||||
|
course,
|
||||||
|
address,
|
||||||
|
data.message.order_id
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let rzp1 = new Razorpay(data.message);
|
||||||
|
rzp1.open();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handle_success = (response, course, address, order_id) => {
|
||||||
|
frappe.call({
|
||||||
|
method: "lms.lms.doctype.lms_course.lms_course.verify_payment",
|
||||||
|
args: {
|
||||||
|
response: response,
|
||||||
|
course: course,
|
||||||
|
address: address,
|
||||||
|
order_id: order_id,
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __("Payment Successful"),
|
||||||
|
indicator: "green",
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = data.message;
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
23
lms/www/billing/billing.py
Normal file
23
lms/www/billing/billing.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
|
def get_context(context):
|
||||||
|
course_name = frappe.form_dict.course
|
||||||
|
|
||||||
|
if not course_name:
|
||||||
|
raise ValueError(_("Course is required."))
|
||||||
|
|
||||||
|
if frappe.session.user == "Guest":
|
||||||
|
raise frappe.PermissionError(_("You are not allowed to access this page."))
|
||||||
|
|
||||||
|
membership = frappe.db.exists(
|
||||||
|
"LMS Batch Membership", {"member": frappe.session.user, "course": course_name}
|
||||||
|
)
|
||||||
|
|
||||||
|
if membership:
|
||||||
|
raise frappe.PermissionError(_("You are already enrolled for this course"))
|
||||||
|
|
||||||
|
context.course = frappe.db.get_value(
|
||||||
|
"LMS Course", course_name, ["title", "name", "course_price", "currency"], as_dict=True
|
||||||
|
)
|
||||||
@@ -129,8 +129,8 @@ def get_class_course_details(class_courses):
|
|||||||
"upcoming",
|
"upcoming",
|
||||||
"short_introduction",
|
"short_introduction",
|
||||||
"image",
|
"image",
|
||||||
"paid_certificate",
|
"paid_course",
|
||||||
"price_certificate",
|
"course_price",
|
||||||
"enable_certification",
|
"enable_certification",
|
||||||
"currency",
|
"currency",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
{{ Description(course) }}
|
{{ Description(course) }}
|
||||||
{{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }}
|
{{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }}
|
||||||
{% if course.status == "Approved" and not frappe.utils.cint(course.upcoming) %}
|
{% if course.status == "Approved" and not frappe.utils.cint(course.upcoming) %}
|
||||||
{% include "lms/templates/reviews.html" %}
|
{% include "lms/templates/reviews.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +122,10 @@
|
|||||||
|
|
||||||
{{ Notes(course) }}
|
{{ Notes(course) }}
|
||||||
|
|
||||||
|
<div class="vertically-center mb-3 bold-heading">
|
||||||
|
{{ frappe.utils.fmt_money(course.course_price, 0, course.currency) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="vertically-center mb-3">
|
<div class="vertically-center mb-3">
|
||||||
<svg class="icon icon-md mr-1">
|
<svg class="icon icon-md mr-1">
|
||||||
<use class="" href="#icon-users">
|
<use class="" href="#icon-users">
|
||||||
@@ -136,18 +140,8 @@
|
|||||||
{{ get_lessons(course.name, None, False) }} {{ _("Lessons") }}
|
{{ get_lessons(course.name, None, False) }} {{ _("Lessons") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if course.enable_certification %}
|
|
||||||
<div class="vertically-center mb-3">
|
|
||||||
<svg class="icon icon-md mr-1">
|
|
||||||
<use href="#icon-badge"></use>
|
|
||||||
</svg>
|
|
||||||
{{ _("Get Certified") }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ SlotModal(course) }}
|
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
@@ -204,18 +198,18 @@
|
|||||||
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
||||||
|
|
||||||
<div class="all-cta">
|
<div class="all-cta">
|
||||||
{% if is_instructor(course.name) and not course.published and course.status != "Under Review" %}
|
{% if is_instructor and not course.published and course.status != "Under Review" %}
|
||||||
<div class="btn btn-primary wide-button" id="submit-for-review" data-course="{{ course.name | urlencode }}">
|
<div class="btn btn-primary wide-button" id="submit-for-review" data-course="{{ course.name | urlencode }}">
|
||||||
{{ _("Submit for Review") }}
|
{{ _("Submit for Review") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% elif is_instructor(course.name) and lesson_index %}
|
{% elif is_instructor and lesson_index %}
|
||||||
<a class="btn btn-primary wide-button" id="continue-learning"
|
<a class="btn btn-primary wide-button" id="continue-learning"
|
||||||
href="{{ get_lesson_url(course.name, lesson_index) }}{{ course.query_parameter }}">
|
href="{{ get_lesson_url(course.name, lesson_index) }}{{ course.query_parameter }}">
|
||||||
{{ _("Checkout Course") }}
|
{{ _("Checkout Course") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% elif course.upcoming and not is_user_interested and not is_instructor(course.name) %}
|
{% elif course.upcoming and not is_user_interested and not is_instructor %}
|
||||||
<div class="btn btn-secondary wide-button notify-me" data-course="{{course.name | urlencode}}">
|
<div class="btn btn-secondary wide-button notify-me" data-course="{{course.name | urlencode}}">
|
||||||
{{ _("Notify me when available") }}
|
{{ _("Notify me when available") }}
|
||||||
</div>
|
</div>
|
||||||
@@ -231,8 +225,13 @@
|
|||||||
{{ _("Continue Learning") }}
|
{{ _("Continue Learning") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
{% elif course.paid_course and not is_instructor %}
|
||||||
|
<a class="btn btn-primary wide-button" href="/billing/{{ course.name | urlencode }}">
|
||||||
|
{{ _("Buy This Course") }}
|
||||||
|
</a>
|
||||||
|
|
||||||
{% elif show_start_learing_cta(course, membership) %}
|
{% elif show_start_learing_cta(course, membership) %}
|
||||||
<div class="btn btn-primary wide-button join-batch" data-course="{{ course.name | urlencode }}">
|
<div class="btn btn-primary wide-button enroll-in-course" data-course="{{ course.name | urlencode }}">
|
||||||
{{ _("Start Learning") }}
|
{{ _("Start Learning") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -245,11 +244,6 @@
|
|||||||
{{ _("Get Certificate") }}
|
{{ _("Get Certificate") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% elif eligible_for_evaluation %}
|
|
||||||
<a class="btn btn-secondary wide-button mt-2" id="apply-certificate" data-course="{{ course.name }}">
|
|
||||||
{{ _("Apply for Certificate") }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
{% elif course.grant_certificate_after == "Completion" and progress == 100 %}
|
{% elif course.grant_certificate_after == "Completion" and progress == 100 %}
|
||||||
<div class="btn btn-secondary wide-button mt-2" id="certification" data-course="{{ course.name }}">
|
<div class="btn btn-secondary wide-button mt-2" id="certification" data-course="{{ course.name }}">
|
||||||
{{ _("Get Certificate") }}
|
{{ _("Get Certificate") }}
|
||||||
@@ -257,7 +251,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if is_instructor(course.name) or has_course_moderator_role() %}
|
{% if is_instructor or has_course_moderator_role() %}
|
||||||
<a class="btn btn-secondary wide-button mt-2" title="Edit Course" href="/courses/{{ course.name }}/edit">
|
<a class="btn btn-secondary wide-button mt-2" title="Edit Course" href="/courses/{{ course.name }}/edit">
|
||||||
<!-- <svg class="icon icon-md">
|
<!-- <svg class="icon icon-md">
|
||||||
<use href="#icon-edit"></use>
|
<use href="#icon-edit"></use>
|
||||||
@@ -267,9 +261,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
@@ -279,17 +270,7 @@
|
|||||||
{{ _("You have opted to be notified for this course. You will receive an email when the course becomes available.") }}
|
{{ _("You have opted to be notified for this course. You will receive an email when the course becomes available.") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if certificate_request and not certificate %}
|
{% if course.status == "Under Review" and is_instructor %}
|
||||||
<p class="mb-2">
|
|
||||||
<b>
|
|
||||||
{{ _("Evaluation On: ") }}
|
|
||||||
</b>
|
|
||||||
{{ _("{0} at {1}").format(frappe.utils.format_date(certificate_request.date, "medium"),
|
|
||||||
frappe.utils.format_time(certificate_request.start_time, "short")) }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if course.status == "Under Review" and is_instructor(course.name) %}
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ _("This course is currently under review. Once the review is complete, the System Admins will publish it on the website.") }}
|
{{ _("This course is currently under review. Once the review is complete, the System Admins will publish it on the website.") }}
|
||||||
</div>
|
</div>
|
||||||
@@ -300,55 +281,4 @@
|
|||||||
{{ _("You have exceeded the maximum number of attempts allowed to appear for evaluations of this course.") }}
|
{{ _("You have exceeded the maximum number of attempts allowed to appear for evaluations of this course.") }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
<!-- Modal for Slots -->
|
|
||||||
{% macro SlotModal(course) %}
|
|
||||||
<div class="modal fade" id="slot-modal" tabindex="-1" role="dialog" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<div class="modal-title">{{ _("Pick a Slot") }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="slot-form">
|
|
||||||
<p class="">{{ _("This course requires you to complete an evaluation to get certified. Please pick a slot based on your convenience for the evaluations. ") }}</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="clearfix">
|
|
||||||
<label class="control-label reqd" style="padding-right: 0px;">{{ _("Date") }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="control-input-wrapper">
|
|
||||||
<div class="control-input">
|
|
||||||
<input type="date" class="input-with-feedback form-control bold" data-fieldtype="Date" data-course="{{ course.name | urlencode }}"
|
|
||||||
id="slot-date" min="{{ frappe.utils.format_date(frappe.utils.add_days(frappe.utils.getdate(), 1), 'yyyy-mm-dd') }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="clearfix">
|
|
||||||
<label class="control-label reqd slot-label hide" style="padding-right: 0px;">{{ _("Slots") }}</label>
|
|
||||||
</div>
|
|
||||||
<div class="control-input-wrapper">
|
|
||||||
<div class="control-input">
|
|
||||||
<div class="slots"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p id="no-slots-message" class="small text-danger hide"> {{ _("There are no slots available on this day.") }} </p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-secondary btn-sm pull-right mr-2 close-slot-modal" data-dismiss="modal" aria-label="Close">
|
|
||||||
{{ _("Discard") }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn btn-primary btn-sm pull-right" data-course="{{ course.name | urlencode}}" id="submit-slot">
|
|
||||||
{{ _("Submit") }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
@@ -20,26 +20,6 @@ frappe.ready(() => {
|
|||||||
$("#submit-for-review").click((e) => {
|
$("#submit-for-review").click((e) => {
|
||||||
submit_for_review(e);
|
submit_for_review(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#apply-certificate").click((e) => {
|
|
||||||
apply_cetificate(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#slot-date").on("change", (e) => {
|
|
||||||
display_slots(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#submit-slot").click((e) => {
|
|
||||||
submit_slot(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".close-slot-modal").click((e) => {
|
|
||||||
close_slot_modal(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on("click", ".slot", (e) => {
|
|
||||||
select_slot(e);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const hide_wrapped_mentor_cards = () => {
|
const hide_wrapped_mentor_cards = () => {
|
||||||
@@ -162,99 +142,3 @@ const submit_for_review = (e) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const apply_cetificate = (e) => {
|
|
||||||
$("#slot-modal").modal("show");
|
|
||||||
};
|
|
||||||
|
|
||||||
const submit_slot = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const slot = window.selected_slot;
|
|
||||||
frappe.call({
|
|
||||||
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request",
|
|
||||||
args: {
|
|
||||||
course: slot.data("course"),
|
|
||||||
date: $("#slot-date").val(),
|
|
||||||
day: slot.data("day"),
|
|
||||||
start_time: slot.data("start"),
|
|
||||||
end_time: slot.data("end"),
|
|
||||||
},
|
|
||||||
callback: (data) => {
|
|
||||||
$("#slot-modal").modal("hide");
|
|
||||||
frappe.show_alert(
|
|
||||||
{
|
|
||||||
message: __(
|
|
||||||
"Your slot has been booked. Prepare well for the evaluations."
|
|
||||||
),
|
|
||||||
indicator: "green",
|
|
||||||
},
|
|
||||||
3
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 3000);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const display_slots = (e) => {
|
|
||||||
frappe.call({
|
|
||||||
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
|
|
||||||
args: {
|
|
||||||
course: $(e.currentTarget).data("course"),
|
|
||||||
date: $(e.currentTarget).val(),
|
|
||||||
},
|
|
||||||
callback: (data) => {
|
|
||||||
let options = "";
|
|
||||||
data.message.forEach((obj) => {
|
|
||||||
options += `<button type="button" class="btn btn-sm btn-secondary mb-3 mr-3 slot hide"
|
|
||||||
data-course="${$(e.currentTarget).data("course")}"
|
|
||||||
data-day="${obj.day}" data-start="${
|
|
||||||
obj.start_time
|
|
||||||
}" data-end="${obj.end_time}">
|
|
||||||
${format_time(obj.start_time)} - ${format_time(
|
|
||||||
obj.end_time
|
|
||||||
)}</button>`;
|
|
||||||
});
|
|
||||||
e.preventDefault();
|
|
||||||
$("#slot-modal .slots").html(options);
|
|
||||||
const weekday = [
|
|
||||||
"Sunday",
|
|
||||||
"Monday",
|
|
||||||
"Tuesday",
|
|
||||||
"Wednesday",
|
|
||||||
"Thursday",
|
|
||||||
"Friday",
|
|
||||||
"Saturday",
|
|
||||||
];
|
|
||||||
const day = weekday[new Date($(e.currentTarget).val()).getDay()];
|
|
||||||
|
|
||||||
$(".slot").addClass("hide");
|
|
||||||
$(".slot-label").addClass("hide");
|
|
||||||
|
|
||||||
if ($(`[data-day='${day}']`).length) {
|
|
||||||
$(".slot-label").removeClass("hide");
|
|
||||||
$(`[data-day='${day}']`).removeClass("hide");
|
|
||||||
$("#no-slots-message").addClass("hide");
|
|
||||||
} else {
|
|
||||||
$("#no-slots-message").removeClass("hide");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const select_slot = (e) => {
|
|
||||||
$(".slot").removeClass("btn-outline-primary");
|
|
||||||
$(e.currentTarget).addClass("btn-outline-primary");
|
|
||||||
window.selected_slot = $(e.currentTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
const format_time = (time) => {
|
|
||||||
let date = moment(new Date()).format("ddd MMM DD YYYY");
|
|
||||||
return moment(`${date} ${time}`).format("HH:mm a");
|
|
||||||
};
|
|
||||||
|
|
||||||
const close_slot_modal = (e) => {
|
|
||||||
$("#slot-date").val("");
|
|
||||||
$(".slot-label").addClass("hide");
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -52,13 +52,10 @@ def set_course_context(context, course_name):
|
|||||||
"disable_self_learning",
|
"disable_self_learning",
|
||||||
"status",
|
"status",
|
||||||
"video_link",
|
"video_link",
|
||||||
"enable_certification",
|
"paid_course",
|
||||||
"grant_certificate_after",
|
"course_price",
|
||||||
"paid_certificate",
|
|
||||||
"price_certificate",
|
|
||||||
"currency",
|
"currency",
|
||||||
"max_attempts",
|
"grant_certificate_after",
|
||||||
"duration",
|
|
||||||
],
|
],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
@@ -79,7 +76,7 @@ def set_course_context(context, course_name):
|
|||||||
frappe.db.get_value(
|
frappe.db.get_value(
|
||||||
"LMS Course",
|
"LMS Course",
|
||||||
csr.course,
|
csr.course,
|
||||||
["name", "upcoming", "title", "image", "enable_certification"],
|
["name", "upcoming", "title", "image"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -91,10 +88,10 @@ def set_course_context(context, course_name):
|
|||||||
"?batch=" + membership.batch if membership and membership.batch else ""
|
"?batch=" + membership.batch if membership and membership.batch else ""
|
||||||
)
|
)
|
||||||
context.membership = membership
|
context.membership = membership
|
||||||
|
context.is_instructor = is_instructor(course.name)
|
||||||
context.certificate = is_certified(course.name)
|
context.certificate = is_certified(course.name)
|
||||||
eval_details = get_evaluation_details(course.name)
|
eval_details = get_evaluation_details(course.name)
|
||||||
context.eligible_for_evaluation = eval_details.eligible
|
context.eligible_for_evaluation = eval_details.eligible
|
||||||
context.certificate_request = eval_details.request
|
|
||||||
context.no_of_attempts = eval_details.no_of_attempts
|
context.no_of_attempts = eval_details.no_of_attempts
|
||||||
if context.course.upcoming:
|
if context.course.upcoming:
|
||||||
context.is_user_interested = get_user_interest(context.course.name)
|
context.is_user_interested = get_user_interest(context.course.name)
|
||||||
|
|||||||
@@ -157,6 +157,43 @@
|
|||||||
<img {% if course.image %} class="image-preview" src="{{ course.image }}" {% endif %}>
|
<img {% if course.image %} class="image-preview" src="{{ course.image }}" {% endif %}>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group">
|
||||||
|
<label for="paid_course" class="vertically-center mb-0">
|
||||||
|
<input type="checkbox" id="paid-course" {% if course.paid_course %} checked {% endif %}>
|
||||||
|
{{ _("Paid Course") }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group price-field {% if not course.paid_course %} hide {% endif %}">
|
||||||
|
<div class="field-label {% if course.paid_course %} reqd {% endif %}">
|
||||||
|
{{ _("Course Price") }}
|
||||||
|
</div>
|
||||||
|
<div class="field-description">
|
||||||
|
{{ _("The price of this course.") }}
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<input id="course-price" type="number" class="field-input" {% if course.course_price %} value="{{ course.course_price }}" {% endif %}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group price-field {% if not course.paid_course %} hide {% endif %}">
|
||||||
|
<div class="field-label {% if course.paid_course %} reqd {% endif %}">
|
||||||
|
{{ _("Currency") }}
|
||||||
|
</div>
|
||||||
|
<div class="field-description">
|
||||||
|
{{ _("The currency in which users will pay for this course.") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select class="field-input" id="currency">
|
||||||
|
<option></option>
|
||||||
|
{% for currency in currencies %}
|
||||||
|
<option value="{{ currency }}" {% if currency == course.currency %} selected {% endif %}>
|
||||||
|
{{ currency}}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="field-label">
|
<div class="field-label">
|
||||||
{{ _("Instructor") }}
|
{{ _("Instructor") }}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ frappe.ready(() => {
|
|||||||
$(".btn-upload").click((e) => {
|
$(".btn-upload").click((e) => {
|
||||||
upload_file(e);
|
upload_file(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#paid-course").click((e) => {
|
||||||
|
setup_paid_course(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const create_tag = (e) => {
|
const create_tag = (e) => {
|
||||||
@@ -79,6 +83,9 @@ const save_course = (e) => {
|
|||||||
: "",
|
: "",
|
||||||
published: $("#published").prop("checked") ? 1 : 0,
|
published: $("#published").prop("checked") ? 1 : 0,
|
||||||
upcoming: $("#upcoming").prop("checked") ? 1 : 0,
|
upcoming: $("#upcoming").prop("checked") ? 1 : 0,
|
||||||
|
paid_course: $("#paid-course").prop("checked") ? 1 : 0,
|
||||||
|
course_price: $("#course-price").val(),
|
||||||
|
currency: $("#currency").val(),
|
||||||
},
|
},
|
||||||
callback: (data) => {
|
callback: (data) => {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
@@ -167,3 +174,13 @@ const upload_file = (e) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setup_paid_course = (e) => {
|
||||||
|
if ($(e.target).prop("checked")) {
|
||||||
|
$(".price-field").removeClass("hide");
|
||||||
|
$(".price-field").find(".field-label").addClass("reqd");
|
||||||
|
} else {
|
||||||
|
$(".price-field").addClass("hide");
|
||||||
|
$(".price-field").find(".field-label").removeClass("reqd");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ def get_context(context):
|
|||||||
context.member = frappe.db.get_value(
|
context.member = frappe.db.get_value(
|
||||||
"User", frappe.session.user, ["full_name", "username"], as_dict=True
|
"User", frappe.session.user, ["full_name", "username"], as_dict=True
|
||||||
)
|
)
|
||||||
|
context.currencies = frappe.get_all("Currency", {"enabled": 1}, pluck="currency_name")
|
||||||
|
|
||||||
|
|
||||||
def set_course_context(context, course_name):
|
def set_course_context(context, course_name):
|
||||||
@@ -51,8 +52,8 @@ def set_course_context(context, course_name):
|
|||||||
"video_link",
|
"video_link",
|
||||||
"enable_certification",
|
"enable_certification",
|
||||||
"grant_certificate_after",
|
"grant_certificate_after",
|
||||||
"paid_certificate",
|
"paid_course",
|
||||||
"price_certificate",
|
"course_price",
|
||||||
"currency",
|
"currency",
|
||||||
"max_attempts",
|
"max_attempts",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ def get_courses():
|
|||||||
"title",
|
"title",
|
||||||
"short_introduction",
|
"short_introduction",
|
||||||
"image",
|
"image",
|
||||||
"enable_certification",
|
"paid_course",
|
||||||
"paid_certificate",
|
"course_price",
|
||||||
"price_certificate",
|
|
||||||
"currency",
|
"currency",
|
||||||
"creation",
|
"creation",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ markdown
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
lxml
|
lxml
|
||||||
cairocffi
|
cairocffi
|
||||||
html2image
|
html2image
|
||||||
|
razorpay
|
||||||
Reference in New Issue
Block a user