diff --git a/lms/hooks.py b/lms/hooks.py index 1bb0f1f0..f8796e7e 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -188,6 +188,10 @@ website_route_rules = [ "from_route": "/quiz-submission//", "to_route": "quiz_submission/quiz_submission", }, + { + "from_route": "/billing/", + "to_route": "billing/billing", + }, ] website_redirects = [ diff --git a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json index 903f66ce..10a8e93d 100644 --- a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -12,6 +12,14 @@ "member", "member_name", "member_username", + "billing_information_section", + "address", + "amount", + "currency", + "column_break_rvzn", + "order_id", + "payment_id", + "payment_received", "section_break_8", "cohort", "subgroup", @@ -112,11 +120,54 @@ { "fieldname": "section_break_8", "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, "links": [], - "modified": "2022-10-10 12:38:17.839526", + "modified": "2023-08-17 13:52:49.450499", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch Membership", @@ -141,4 +192,4 @@ "sort_order": "DESC", "states": [], "title_field": "member_name" -} +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_course/lms_course.js b/lms/lms/doctype/lms_course/lms_course.js index 07bd2d30..71c6be3d 100644 --- a/lms/lms/doctype/lms_course/lms_course.js +++ b/lms/lms/doctype/lms_course/lms_course.js @@ -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 () { return { filters: { @@ -29,5 +21,12 @@ frappe.ui.form.on("LMS Course", { }, refresh: (frm) => { 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); + }); }, }); diff --git a/lms/lms/doctype/lms_course/lms_course.json b/lms/lms/doctype/lms_course/lms_course.json index 11e45195..69deefc3 100644 --- a/lms/lms/doctype/lms_course/lms_course.json +++ b/lms/lms/doctype/lms_course/lms_course.json @@ -32,19 +32,18 @@ "description", "chapters", "related_courses", + "pricing_section", + "paid_course", + "currency", + "course_price", "certification_section", "enable_certification", "expiry", - "section_break_23", + "max_attempts", + "column_break_rxww", "grant_certificate_after", "evaluator", - "column_break_26", - "max_attempts", - "duration", - "pricing_section", - "paid_certificate", - "currency", - "price_certificate" + "duration" ], "fields": [ { @@ -170,13 +169,6 @@ "fieldname": "column_break_12", "fieldtype": "Column Break" }, - { - "default": "0", - "depends_on": "enable_certification", - "fieldname": "paid_certificate", - "fieldtype": "Check", - "label": "Paid Certificate" - }, { "depends_on": "enable_certification", "fieldname": "grant_certificate_after", @@ -193,24 +185,16 @@ "options": "Course Evaluator" }, { - "depends_on": "eval: doc.grant_certificate_after == \"Evaluation\"", "fieldname": "pricing_section", "fieldtype": "Section Break", "label": "Pricing" }, { - "depends_on": "paid_certificate", - "fieldname": "price_certificate", - "fieldtype": "Currency", - "label": "Certificate Price", - "mandatory_depends_on": "paid_certificate" - }, - { - "depends_on": "paid_certificate", + "depends_on": "paid_course", "fieldname": "currency", "fieldtype": "Link", "label": "Currency", - "mandatory_depends_on": "paid_certificate", + "mandatory_depends_on": "paid_course", "options": "Currency" }, { @@ -228,11 +212,21 @@ "options": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12" }, { - "fieldname": "section_break_23", - "fieldtype": "Section Break" + "default": "0", + "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" } ], @@ -260,39 +254,12 @@ } ], "make_attachments_public": 1, - "modified": "2023-05-11 17:08:19.763405", + "modified": "2023-08-02 12:07:26.354110", "modified_by": "Administrator", "module": "LMS", "name": "LMS Course", "owner": "Administrator", - "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 - } - ], + "permissions": [], "search_fields": "title", "show_title_field_in_link": 1, "sort_field": "creation", diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index 057ab74c..d4144e0a 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -5,10 +5,12 @@ import json import random import frappe 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 lms.lms.utils import get_chapters, can_create_courses from ...utils import generate_slug, validate_image +from frappe import _ +import razorpay class LMSCourse(Document): @@ -211,6 +213,9 @@ def save_course( published, upcoming, image=None, + paid_course=False, + course_price=None, + currency=None, ): if not can_create_courses(): return @@ -230,6 +235,9 @@ def save_course( "tags": tags, "published": cint(published), "upcoming": cint(upcoming), + "paid_course": cint(paid_course), + "course_price": course_price, + "currency": currency, } ) doc.save(ignore_permissions=True) @@ -354,3 +362,115 @@ def reorder_chapter(chapter_array): "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) + ) diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index f3ed9073..4c8b4d35 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -6,16 +6,23 @@ "engine": "InnoDB", "field_order": [ "default_home", - "send_calendar_invite_for_evaluations", - "allow_student_progress", - "column_break_zdel", - "is_onboarding_complete", "force_profile_completion", - "section_break_szgq", - "search_placeholder", - "portal_course_creation", - "column_break_2", + "is_onboarding_complete", + "column_break_zdel", "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_section", "terms_of_use", @@ -37,6 +44,7 @@ "default": "https://livecode.dev.fossunited.org", "fieldname": "livecode_url", "fieldtype": "Data", + "hidden": 1, "label": "LiveCode URL" }, { @@ -163,7 +171,8 @@ }, { "fieldname": "section_break_szgq", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Class Settings" }, { "fieldname": "signup_settings_tab", @@ -180,12 +189,46 @@ "fieldname": "allow_student_progress", "fieldtype": "Check", "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, "issingle": 1, "links": [], - "modified": "2023-04-17 12:54:44.706101", + "modified": "2023-08-02 18:59:01.267732", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index d472e6cf..96da419c 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -475,7 +475,7 @@ def get_evaluation_details(course, member=None): def format_amount(amount, currency): amount_reduced = amount / 1000 if amount_reduced < 1: - return amount + return fmt_money(amount, 0, currency) precision = 0 if amount % 1000 == 0 else 1 return _("{0}k").format(fmt_money(amount_reduced, precision, currency)) @@ -559,7 +559,9 @@ def get_courses_under_review(): "title", "short_introduction", "image", - "enable_certification", + "paid_course", + "course_price", + "currency", "status", "published", ], diff --git a/lms/lms/widgets/CourseCard.html b/lms/lms/widgets/CourseCard.html index 144be5d5..edda4130 100644 --- a/lms/lms/widgets/CourseCard.html +++ b/lms/lms/widgets/CourseCard.html @@ -58,19 +58,12 @@ {{ frappe.utils.flt(course.avg_rating, frappe.get_system_settings("float_precision") or 3) }} {% endif %} - - {% if course.paid_certificate %} -
- - - - - {{ format_amount(course.price_certificate, course.currency) }} - -
- {% endif %} -
{{ course.title }}
+ +
+ {{ course.title }} +
+
{{ course.short_introduction }}
@@ -86,7 +79,8 @@ {% endif %} {% if read_only %} diff --git a/lms/lms/widgets/NoPreviewModal.html b/lms/lms/widgets/NoPreviewModal.html index 0d9c5767..c6f06aa5 100644 --- a/lms/lms/widgets/NoPreviewModal.html +++ b/lms/lms/widgets/NoPreviewModal.html @@ -30,7 +30,7 @@ {{ _("Notify me when available") }} {% elif show_start_learing_cta(course, membership) %} - {% endif %} diff --git a/lms/overrides/user.py b/lms/overrides/user.py index b561bc0e..17e55859 100644 --- a/lms/overrides/user.py +++ b/lms/overrides/user.py @@ -103,8 +103,8 @@ def get_enrolled_courses(): "short_introduction", "image", "enable_certification", - "paid_certificate", - "price_certificate", + "paid_course", + "course_price", "currency", "published", "creation", @@ -153,7 +153,9 @@ def get_authored_courses(member=None, only_published=True): "title", "short_introduction", "image", - "enable_certification", + "paid_course", + "course_price", + "currency", "status", "published", "creation", diff --git a/lms/patches.txt b/lms/patches.txt index f0d194df..bcabe386 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -60,4 +60,5 @@ lms.patches.v1_0.create_class_evaluator_role execute:frappe.permissions.reset_perms("LMS Class") execute:frappe.permissions.reset_perms("Course Evaluator") execute:frappe.permissions.reset_perms("LMS Certificate Request") -execute:frappe.permissions.reset_perms("LMS Certificate Evaluation") \ No newline at end of file +execute:frappe.permissions.reset_perms("LMS Certificate Evaluation") +lms.patches.v1_0.paid_certificate_to_paid_course \ No newline at end of file diff --git a/lms/patches/v1_0/paid_certificate_to_paid_course.py b/lms/patches/v1_0/paid_certificate_to_paid_course.py new file mode 100644 index 00000000..88ec708b --- /dev/null +++ b/lms/patches/v1_0/paid_certificate_to_paid_course.py @@ -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, + }, + ) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 803e2db5..77ca8197 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -415,11 +415,13 @@ input[type=checkbox] { } .course-card-footer { - margin-top: auto; + display: flex; + justify-content: space-between; + margin-top: auto; } -.course-card-footer .avatar-group { - display: inherit; +.course-price { + font-weight: 500; } .view-course-link { @@ -552,6 +554,11 @@ input[type=checkbox] { } } +.course-card-instructors { + display: flex; + align-items: center; +} + .course-card-wide-content { display: flex; flex-direction: column; @@ -2285,6 +2292,13 @@ select { 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; +} -} \ No newline at end of file diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index c424f598..6911b0a9 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -2,8 +2,8 @@ frappe.ready(() => { setup_file_size(); pin_header(); - $(".join-batch").click((e) => { - join_course(e); + $(".enroll-in-course").click((e) => { + enroll_in_course(e); }); $(".notify-me").click((e) => { @@ -72,7 +72,7 @@ const file_size = (value) => { return value; }; -const join_course = (e) => { +const enroll_in_course = (e) => { e.preventDefault(); let course = $(e.currentTarget).attr("data-course"); if (frappe.session.user == "Guest") { @@ -205,7 +205,7 @@ const expand_the_first_chapter = () => { const expand_the_active_chapter = () => { let selector = $(".course-home-headings.title"); - console.log(selector); + if (selector.length && $(".course-details-page").length) { expand_for_course_details(selector); } else if ($(".active-lesson").length) { diff --git a/lms/templates/reviews_cta.html b/lms/templates/reviews_cta.html index 163e1f6c..dfb06f44 100644 --- a/lms/templates/reviews_cta.html +++ b/lms/templates/reviews_cta.html @@ -2,12 +2,12 @@ {{ _("Write a review") }} -{% elif not is_instructor(course.name) and frappe.session.user == "Guest" %} +{% elif not is_instructor and frappe.session.user == "Guest" %} {{ _("Write a review") }} {% elif show_start_learing_cta(course, membership) %} -
+
{{ _("Start Learning") }}
{% endif %} diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index ee7fd13d..bf4a8d77 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -158,7 +158,7 @@ {{ render_html(lesson) }} {% else %} - {% set course_link = "" + _('here') + "" %} + {% set course_link = "" + _('here') + "" %}
{{ _("There is no preview available for this lesson. Please join the course to access it. diff --git a/lms/www/billing/__init__.py b/lms/www/billing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/www/billing/billing.html b/lms/www/billing/billing.html new file mode 100644 index 00000000..31e84e04 --- /dev/null +++ b/lms/www/billing/billing.html @@ -0,0 +1,68 @@ +{% extends "lms/templates/lms_base.html" %} +{% block title %} + {{ course.title if course.title else _("New Course") }} +{% endblock %} + + +{% block page_content %} +
+ +
+{% endblock %} + +{% macro Header() %} +
+
+ {{ _("Order Details") }} +
+
+ {{ _("Enter the billing information and complete the payment to purchase this course.") }} +
+
+{% endmacro %} + +{% macro CourseDetails() %} +
+
+
+
+ {{ _("Course Name: ") }} {{ course.title }} +
+
+ +
+
+ {{ _("Total Price: ") }} {{ frappe.utils.fmt_money(course.course_price, 2, course.currency) }} +
+
+
+
+{% endmacro %} + +{% macro BillingDetails() %} +
+
+ {{ _("Billing Details") }} +
+
+ +
+{% endmacro %} + +{%- block script %} +{{ super() }} + + +{% endblock %} diff --git a/lms/www/billing/billing.js b/lms/www/billing/billing.js new file mode 100644 index 00000000..6d2cf41f --- /dev/null +++ b/lms/www/billing/billing.js @@ -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); + }, + }); +}; diff --git a/lms/www/billing/billing.py b/lms/www/billing/billing.py new file mode 100644 index 00000000..562e6969 --- /dev/null +++ b/lms/www/billing/billing.py @@ -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 + ) diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index 589d6db0..e1b862e5 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -129,8 +129,8 @@ def get_class_course_details(class_courses): "upcoming", "short_introduction", "image", - "paid_certificate", - "price_certificate", + "paid_course", + "course_price", "enable_certification", "currency", ], diff --git a/lms/www/courses/course.html b/lms/www/courses/course.html index c969dd2e..202eb4eb 100644 --- a/lms/www/courses/course.html +++ b/lms/www/courses/course.html @@ -15,7 +15,7 @@ {{ Description(course) }} {{ widgets.CourseOutline(course=course, membership=membership, is_user_interested=is_user_interested) }} {% if course.status == "Approved" and not frappe.utils.cint(course.upcoming) %} - {% include "lms/templates/reviews.html" %} + {% include "lms/templates/reviews.html" %} {% endif %}
@@ -122,6 +122,10 @@ {{ Notes(course) }} +
+ {{ frappe.utils.fmt_money(course.course_price, 0, course.currency) }} +
+
@@ -136,18 +140,8 @@ {{ get_lessons(course.name, None, False) }} {{ _("Lessons") }}
- {% if course.enable_certification %} -
- - - - {{ _("Get Certified") }} -
- {% endif %} - - {{ SlotModal(course) }} {% endmacro %} @@ -204,18 +198,18 @@ membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
- {% 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" %}
{{ _("Submit for Review") }}
- {% elif is_instructor(course.name) and lesson_index %} + {% elif is_instructor and lesson_index %} {{ _("Checkout Course") }} - {% 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 %}
{{ _("Notify me when available") }}
@@ -231,8 +225,13 @@ {{ _("Continue Learning") }} + {% elif course.paid_course and not is_instructor %} + + {{ _("Buy This Course") }} + + {% elif show_start_learing_cta(course, membership) %} -
+
{{ _("Start Learning") }}
{% endif %} @@ -245,11 +244,6 @@ {{ _("Get Certificate") }} - {% elif eligible_for_evaluation %} - - {{ _("Apply for Certificate") }} - - {% elif course.grant_certificate_after == "Completion" and progress == 100 %}
{{ _("Get Certificate") }} @@ -257,7 +251,7 @@ {% endif %} {% endif %} - {% if is_instructor(course.name) or has_course_moderator_role() %} + {% if is_instructor or has_course_moderator_role() %} -{% macro SlotModal(course) %} - -{% endmacro %} - +{% endmacro %} \ No newline at end of file diff --git a/lms/www/courses/course.js b/lms/www/courses/course.js index 9196bd44..a3f86b84 100644 --- a/lms/www/courses/course.js +++ b/lms/www/courses/course.js @@ -20,26 +20,6 @@ frappe.ready(() => { $("#submit-for-review").click((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 = () => { @@ -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 += ``; - }); - 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"); -}; diff --git a/lms/www/courses/course.py b/lms/www/courses/course.py index 3af669ed..6ed9ab4b 100644 --- a/lms/www/courses/course.py +++ b/lms/www/courses/course.py @@ -52,13 +52,10 @@ def set_course_context(context, course_name): "disable_self_learning", "status", "video_link", - "enable_certification", - "grant_certificate_after", - "paid_certificate", - "price_certificate", + "paid_course", + "course_price", "currency", - "max_attempts", - "duration", + "grant_certificate_after", ], as_dict=True, ) @@ -79,7 +76,7 @@ def set_course_context(context, course_name): frappe.db.get_value( "LMS Course", csr.course, - ["name", "upcoming", "title", "image", "enable_certification"], + ["name", "upcoming", "title", "image"], as_dict=True, ) ) @@ -91,10 +88,10 @@ def set_course_context(context, course_name): "?batch=" + membership.batch if membership and membership.batch else "" ) context.membership = membership + context.is_instructor = is_instructor(course.name) context.certificate = is_certified(course.name) eval_details = get_evaluation_details(course.name) context.eligible_for_evaluation = eval_details.eligible - context.certificate_request = eval_details.request context.no_of_attempts = eval_details.no_of_attempts if context.course.upcoming: context.is_user_interested = get_user_interest(context.course.name) diff --git a/lms/www/courses/create.html b/lms/www/courses/create.html index 8abc4353..8109085b 100644 --- a/lms/www/courses/create.html +++ b/lms/www/courses/create.html @@ -157,6 +157,43 @@
+
+ +
+ +
+
+ {{ _("Course Price") }} +
+
+ {{ _("The price of this course.") }} +
+
+ +
+
+ +
+
+ {{ _("Currency") }} +
+
+ {{ _("The currency in which users will pay for this course.") }} +
+ + +
+
{{ _("Instructor") }} diff --git a/lms/www/courses/create.js b/lms/www/courses/create.js index 76dc1809..fcbb2e27 100644 --- a/lms/www/courses/create.js +++ b/lms/www/courses/create.js @@ -41,6 +41,10 @@ frappe.ready(() => { $(".btn-upload").click((e) => { upload_file(e); }); + + $("#paid-course").click((e) => { + setup_paid_course(e); + }); }); const create_tag = (e) => { @@ -79,6 +83,9 @@ const save_course = (e) => { : "", published: $("#published").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) => { 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"); + } +}; diff --git a/lms/www/courses/create.py b/lms/www/courses/create.py index 569dea3c..901ce1f2 100644 --- a/lms/www/courses/create.py +++ b/lms/www/courses/create.py @@ -35,6 +35,7 @@ def get_context(context): context.member = frappe.db.get_value( "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): @@ -51,8 +52,8 @@ def set_course_context(context, course_name): "video_link", "enable_certification", "grant_certificate_after", - "paid_certificate", - "price_certificate", + "paid_course", + "course_price", "currency", "max_attempts", ] diff --git a/lms/www/courses/index.py b/lms/www/courses/index.py index 6e1cafd2..fae14436 100644 --- a/lms/www/courses/index.py +++ b/lms/www/courses/index.py @@ -46,9 +46,8 @@ def get_courses(): "title", "short_introduction", "image", - "enable_certification", - "paid_certificate", - "price_certificate", + "paid_course", + "course_price", "currency", "creation", ], diff --git a/requirements.txt b/requirements.txt index d116dede..74bac25a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ markdown beautifulsoup4 lxml cairocffi -html2image \ No newline at end of file +html2image +razorpay \ No newline at end of file