From d5524a8d677e0d33dddbe91ed792e086bb71f7b3 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Sat, 19 Aug 2023 12:24:13 +0530 Subject: [PATCH 01/15] feat: registration information in class student --- .../doctype/class_student/class_student.json | 51 +++++++++++++++---- lms/lms/doctype/lms_class/lms_class.js | 4 +- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lms/lms/doctype/class_student/class_student.json b/lms/lms/doctype/class_student/class_student.json index 302f602f..390ad523 100644 --- a/lms/lms/doctype/class_student/class_student.json +++ b/lms/lms/doctype/class_student/class_student.json @@ -7,30 +7,63 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "class_student", - "student" + "student", + "student_name", + "username", + "column_break_zvlp", + "address", + "amount", + "currency" ], "fields": [ { "fieldname": "student", "fieldtype": "Link", - "hidden": 1, + "reqd": 1, "label": "Student", "options": "User" }, { - "fieldname": "class_student", - "fieldtype": "Link", + "fetch_from": "student.full_name", + "fieldname": "student_name", + "fieldtype": "Data", "in_list_view": 1, - "label": "Class Student", - "options": "Class Student Registration", - "reqd": 1 + "label": "Student Name", + "read_only": 1 + }, + { + "fetch_from": "student.username", + "fieldname": "username", + "fieldtype": "Data", + "label": "Username", + "read_only": 1 + }, + { + "fieldname": "column_break_zvlp", + "fieldtype": "Column Break" + }, + { + "fieldname": "address", + "fieldtype": "Link", + "label": "Address", + "options": "Address" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-18 16:56:34.435008", + "modified": "2023-08-18 19:19:34.701473", "modified_by": "Administrator", "module": "LMS", "name": "Class Student", diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index 342b6ae2..7f3c4299 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -3,10 +3,10 @@ frappe.ui.form.on("LMS Class", { onload: function (frm) { - frm.set_query("class_student", "students", function (doc) { + frm.set_query("student", "students", function (doc) { return { filters: { - class_name: doc.name, + ignore_user_type: 1, }, }; }); From 04ed7f412f81d13c6f54416cc2f03be9de425a38 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 22 Aug 2023 18:34:42 +0530 Subject: [PATCH 02/15] feat: class billing --- lms/hooks.py | 7 +- .../doctype/class_student/class_student.json | 25 +- lms/lms/doctype/lms_class/lms_class.json | 50 ++-- lms/lms/doctype/lms_class/lms_class.py | 36 --- lms/lms/doctype/lms_course/lms_course.py | 115 +--------- lms/lms/utils.py | 166 +++++++++++++- lms/public/css/style.css | 3 + lms/public/icons/symbol-defs.svg | 7 + lms/public/js/common_functions.js | 59 ++++- lms/www/billing/billing.html | 15 +- lms/www/billing/billing.js | 22 +- lms/www/billing/billing.py | 69 +++++- lms/www/classes/class.html | 14 +- lms/www/classes/class.js | 16 +- lms/www/classes/class.py | 13 +- lms/www/classes/class_details.html | 214 ++++++++++++++++++ lms/www/classes/class_details.js | 3 + lms/www/classes/class_details.py | 21 ++ lms/www/classes/index.html | 24 +- lms/www/classes/index.py | 5 +- lms/www/courses/course.html | 10 +- lms/www/utils.py | 13 ++ 22 files changed, 663 insertions(+), 244 deletions(-) create mode 100644 lms/www/classes/class_details.html create mode 100644 lms/www/classes/class_details.js create mode 100644 lms/www/classes/class_details.py diff --git a/lms/hooks.py b/lms/hooks.py index f8796e7e..fc78d28e 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -189,9 +189,13 @@ website_route_rules = [ "to_route": "quiz_submission/quiz_submission", }, { - "from_route": "/billing/", + "from_route": "/billing//", "to_route": "billing/billing", }, + { + "from_route": "/classes/details/", + "to_route": "classes/class_details", + }, ] website_redirects = [ @@ -249,6 +253,7 @@ jinja = { "lms.lms.utils.can_create_courses", "lms.lms.utils.get_telemetry_boot_info", "lms.lms.utils.is_onboarding_complete", + "lms.www.utils.is_student", ], "filters": [], } diff --git a/lms/lms/doctype/class_student/class_student.json b/lms/lms/doctype/class_student/class_student.json index 8b3e9df2..55017ec0 100644 --- a/lms/lms/doctype/class_student/class_student.json +++ b/lms/lms/doctype/class_student/class_student.json @@ -10,10 +10,13 @@ "student", "student_name", "username", - "column_break_zvlp", "address", + "column_break_zvlp", "amount", - "currency" + "currency", + "order_id", + "payment_id", + "payment_received" ], "fields": [ { @@ -59,12 +62,28 @@ "fieldtype": "Link", "label": "Currency", "options": "Currency" + }, + { + "fieldname": "order_id", + "fieldtype": "Data", + "label": "Order ID" + }, + { + "fieldname": "payment_id", + "fieldtype": "Data", + "label": "Payment ID" + }, + { + "default": "0", + "fieldname": "payment_received", + "fieldtype": "Check", + "label": "Payment Received" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-18 19:19:34.701473", + "modified": "2023-08-22 10:41:40.577437", "modified_by": "Administrator", "module": "LMS", "name": "Class Student", diff --git a/lms/lms/doctype/lms_class/lms_class.json b/lms/lms/doctype/lms_class/lms_class.json index dd789b13..b80cfa21 100644 --- a/lms/lms/doctype/lms_class/lms_class.json +++ b/lms/lms/doctype/lms_class/lms_class.json @@ -19,11 +19,16 @@ "category", "column_break_flwy", "seat_count", - "paid_class", "section_break_6", "description", + "prerequisite", "students", "courses", + "section_break_gsac", + "paid_class", + "column_break_iens", + "amount", + "currency", "section_break_ubxi", "custom_component", "assessment_tab", @@ -89,6 +94,7 @@ }, { "default": "0", + "description": "Students will be enrolled in a paid class once they complete the payment", "fieldname": "paid_class", "fieldtype": "Check", "label": "Paid Class" @@ -158,11 +164,39 @@ "fieldname": "schedule_tab", "fieldtype": "Tab Break", "label": "Schedule" + }, + { + "fieldname": "section_break_gsac", + "fieldtype": "Section Break", + "label": "Pricing" + }, + { + "fieldname": "column_break_iens", + "fieldtype": "Column Break" + }, + { + "depends_on": "paid_class", + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "depends_on": "paid_class", + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "prerequisite", + "fieldtype": "Small Text", + "label": "Prerequisite", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-08-10 12:54:44.351907", + "modified": "2023-08-22 11:53:22.248596", "modified_by": "Administrator", "module": "LMS", "name": "LMS Class", @@ -192,18 +226,6 @@ "role": "Moderator", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Class Evaluator", - "share": 1, - "write": 1 } ], "sort_field": "modified", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index b625d01b..31db3d95 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -185,42 +185,6 @@ def authenticate(): return response.json()["access_token"] -@frappe.whitelist() -def create_class( - title, - start_date, - end_date, - description=None, - seat_count=0, - start_time=None, - end_time=None, - medium="Online", - category=None, - name=None, -): - frappe.only_for("Moderator") - if name: - class_details = frappe.get_doc("LMS Class", name) - else: - class_details = frappe.get_doc({"doctype": "LMS Class"}) - - class_details.update( - { - "title": title, - "start_date": start_date, - "end_date": end_date, - "description": description, - "seat_count": seat_count, - "start_time": start_time, - "end_time": end_time, - "medium": medium, - "category": category, - } - ) - class_details.save() - return class_details - - @frappe.whitelist() def fetch_lessons(courses): lessons = [] diff --git a/lms/lms/doctype/lms_course/lms_course.py b/lms/lms/doctype/lms_course/lms_course.py index d4144e0a..505ae7c3 100644 --- a/lms/lms/doctype/lms_course/lms_course.py +++ b/lms/lms/doctype/lms_course/lms_course.py @@ -5,12 +5,11 @@ import json import random import frappe from frappe.model.document import Document -from frappe.utils import cint, validate_phone_number +from frappe.utils import cint 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): @@ -362,115 +361,3 @@ 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/utils.py b/lms/lms/utils.py index 96da419c..72832ad9 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1,6 +1,8 @@ import re import string import frappe +import json +import razorpay from frappe import _ from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result from frappe.desk.doctype.notification_log.notification_log import make_notification_logs @@ -13,9 +15,9 @@ from frappe.utils import ( format_date, get_datetime, getdate, + validate_phone_number, ) from frappe.utils.dateutils import get_period - from lms.lms.md import find_macros, markdown_to_html RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+") @@ -825,3 +827,165 @@ def get_upcoming_evals(student, courses): evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") return upcoming_evals + + +@frappe.whitelist() +def get_payment_options(doctype, docname, phone): + if not frappe.db.exists(doctype, docname): + frappe.throw(_("Invalid document provided.")) + + validate_phone_number(phone, True) + if doctype == "LMS Course": + details = frappe.db.get_value( + "LMS Course", + docname, + ["name", "title", "paid_course", "currency", "course_price as amount"], + as_dict=True, + ) + if not details.paid_course: + frappe.throw(_("This course is free.")) + else: + details = frappe.db.get_value( + "LMS Class", + docname, + ["name", "title", "paid_class", "currency", "amount"], + as_dict=True, + ) + if not details.paid_class: + frappe.throw(_("To join this class, please contact the Administrator.")) + + razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key") + client = get_client() + order = create_order(client, details.amount, details.currency) + + options = { + "key_id": razorpay_key, + "name": frappe.db.get_single_value("Website Settings", "app_name"), + "description": _("Payment for {0} course").format(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, amount, currency): + try: + return client.order.create( + { + "amount": amount * 100, + "currency": currency, + } + ) + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) + + +@frappe.whitelist() +def verify_payment(response, doctype, docname, 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"], + } + ) + + if doctype == "LMS Course": + return create_membership(address, response, docname, client) + else: + return add_student_to_class(address, response, docname, client) + + +def create_membership(address, response, course, client): + try: + address_name = save_address(address) + payment = client.payment.fetch(response["razorpay_payment_id"]) + membership = frappe.new_doc("LMS Batch Membership") + + 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) + ) + + +def add_student_to_class(address, response, classname, client): + try: + address_name = save_address(address) + payment = client.payment.fetch(response["razorpay_payment_id"]) + student = frappe.new_doc("Class Student") + + student.update( + { + "student": frappe.session.user, + "parent": classname, + "parenttype": "LMS Class", + "parentfield": "students", + "address": address_name, + "amount": payment["amount"] / 100, + "currency": payment["currency"], + "payment_received": 1, + "order_id": response["razorpay_order_id"], + "payment_id": response["razorpay_payment_id"], + } + ) + student.save(ignore_permissions=True) + return f"/classes/{classname}" + except Exception as e: + frappe.throw( + _("Error during payment: {0}. Please contact the Administrator.").format(e) + ) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 77ca8197..35401e67 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2302,3 +2302,6 @@ select { padding-right: 1rem !important; } +.class-overlay { + top: 30%; +} \ No newline at end of file diff --git a/lms/public/icons/symbol-defs.svg b/lms/public/icons/symbol-defs.svg index f7823137..102d9efe 100644 --- a/lms/public/icons/symbol-defs.svg +++ b/lms/public/icons/symbol-defs.svg @@ -104,11 +104,18 @@ + + + + + + + diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index 6911b0a9..39adf583 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -320,6 +320,41 @@ const open_class_dialog = () => { default: class_info && class_info.description, reqd: 1, }, + { + fieldtype: "Small Text", + label: __("Prerequisite"), + fieldname: "prerequisite", + default: class_info && class_info.prerequisite, + reqd: 1, + }, + { + fieldtype: "Section Break", + label: __("Pricing"), + fieldname: "pricing", + }, + { + fieldtype: "Check", + label: __("Paid Class"), + fieldname: "paid_class", + default: class_info && class_info.paid_class, + }, + { + fieldtype: "Currency", + label: __("Amount"), + fieldname: "amount", + default: class_info && class_info.amount, + mandatory_depends_on: "paid_class", + depends_on: "paid_class", + }, + { + fieldtype: "Link", + label: __("Currency"), + fieldname: "currency", + options: "Currency", + default: class_info && class_info.currency, + mandatory_depends_on: "paid_class", + depends_on: "paid_class", + }, ], primary_action_label: __("Save"), primary_action: (values) => { @@ -330,19 +365,19 @@ const open_class_dialog = () => { }; const save_class = (values) => { + let method, args; + if (class_info) { + method = "frappe.client.save"; + args = Object.assign(class_info, values); + } else { + method = "frappe.client.insert"; + args = values; + args.doctype = "LMS Class"; + } frappe.call({ - method: "lms.lms.doctype.lms_class.lms_class.create_class", + method: method, args: { - title: values.title, - start_date: values.start_date, - end_date: values.end_date, - description: values.description, - seat_count: values.seat_count, - start_time: values.start_time, - end_time: values.end_time, - medium: values.medium, - category: values.category, - name: class_info && class_info.name, + doc: args, }, callback: (r) => { if (r.message) { @@ -353,7 +388,7 @@ const save_class = (values) => { indicator: "green", }); this.class_dialog.hide(); - window.location.href = `/classes/${r.message.name}`; + window.location.href = `/classes/details/${r.message.name}`; } }, }); diff --git a/lms/www/billing/billing.html b/lms/www/billing/billing.html index 31e84e04..4229d863 100644 --- a/lms/www/billing/billing.html +++ b/lms/www/billing/billing.html @@ -1,6 +1,6 @@ {% extends "lms/templates/lms_base.html" %} {% block title %} - {{ course.title if course.title else _("New Course") }} + {{ title }} {{ _("Billing") }} {% endblock %} @@ -8,7 +8,7 @@
@@ -20,23 +20,24 @@ {{ _("Order Details") }}
- {{ _("Enter the billing information and complete the payment to purchase this course.") }} + {{ _("Enter the billing information and complete the payment to purchase this {0}.").format(module) }}
{% endmacro %} -{% macro CourseDetails() %} +{% macro Details() %}
- {{ _("Course Name: ") }} {{ course.title }} + {% set label = "Course Name" if module == "course" else "Class Name" %} + {{ _(label) }} : {{ title }}
- {{ _("Total Price: ") }} {{ frappe.utils.fmt_money(course.course_price, 2, course.currency) }} + {{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }}
@@ -49,7 +50,7 @@ {{ _("Billing Details") }}
- diff --git a/lms/www/billing/billing.js b/lms/www/billing/billing.js index 6d2cf41f..cc0c6413 100644 --- a/lms/www/billing/billing.js +++ b/lms/www/billing/billing.js @@ -1,6 +1,8 @@ frappe.ready(() => { if ($("#billing-form").length) { - setup_billing(); + frappe.require("controls.bundle.js", () => { + setup_billing(); + }); } $(".btn-pay").click((e) => { @@ -66,19 +68,22 @@ const setup_billing = () => { const generate_payment_link = (e) => { address = this.billing.get_values(); - let course = decodeURIComponent($(e.currentTarget).attr("data-course")); + let doctype = $(e.currentTarget).attr("data-doctype"); + let docname = decodeURIComponent($(e.currentTarget).attr("data-name")); frappe.call({ - method: "lms.lms.doctype.lms_course.lms_course.get_payment_options", + method: "lms.lms.utils.get_payment_options", args: { - course: course, + doctype: doctype, + docname: docname, phone: address.phone, }, callback: (data) => { data.message.handler = (response) => { handle_success( response, - course, + doctype, + docname, address, data.message.order_id ); @@ -89,12 +94,13 @@ const generate_payment_link = (e) => { }); }; -const handle_success = (response, course, address, order_id) => { +const handle_success = (response, doctype, docname, address, order_id) => { frappe.call({ - method: "lms.lms.doctype.lms_course.lms_course.verify_payment", + method: "lms.lms.utils.verify_payment", args: { response: response, - course: course, + doctype: doctype, + docname: docname, address: address, order_id: order_id, }, diff --git a/lms/www/billing/billing.py b/lms/www/billing/billing.py index 562e6969..0b2b7130 100644 --- a/lms/www/billing/billing.py +++ b/lms/www/billing/billing.py @@ -3,21 +3,66 @@ from frappe import _ def get_context(context): - course_name = frappe.form_dict.course - - if not course_name: - raise ValueError(_("Course is required.")) + module = frappe.form_dict.module + docname = frappe.form_dict.modulename 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 module not in ["course", "class"]: + raise ValueError(_("Module is incorrect.")) - if membership: - raise frappe.PermissionError(_("You are already enrolled for this course")) + doctype = "LMS Course" if module == "course" else "LMS Class" + context.module = module + context.docname = docname + context.doctype = doctype - context.course = frappe.db.get_value( - "LMS Course", course_name, ["title", "name", "course_price", "currency"], as_dict=True - ) + if not frappe.db.exists(doctype, docname): + print(doctype, docname) + raise ValueError(_("Module Name is incorrect or does not exist.")) + + if doctype == "LMS Course": + membership = frappe.db.exists( + "LMS Batch Membership", {"member": frappe.session.user, "course": docname} + ) + if membership: + raise frappe.PermissionError(_("You are already enrolled for this course")) + + else: + membership = frappe.db.exists( + "Class Student", {"student": frappe.session.user, "parent": docname} + ) + if membership: + raise frappe.PermissionError(_("You are already enrolled for this class")) + + if doctype == "LMS Course": + course = frappe.db.get_value( + "LMS Course", + docname, + ["title", "name", "paid_course", "course_price", "currency"], + as_dict=True, + ) + + if not course.paid_course: + raise frappe.PermissionError(_("This course is free.")) + + context.title = course.title + context.amount = course.course_price + context.currency = course.currency + + else: + class_info = frappe.db.get_value( + "LMS Class", + docname, + ["title", "name", "paid_class", "amount", "currency"], + as_dict=True, + ) + + if not class_info.paid_class: + raise frappe.PermissionError( + _("To join this class, please contact the Administrator.") + ) + + context.title = class_info.title + context.amount = class_info.amount + context.currency = class_info.currency diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 8726da4b..a0a88962 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -85,12 +85,6 @@ {% macro ClassSections(class_info, class_courses, class_students, flow) %}
- {% if is_moderator %} - - {% endif %} -
diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 4c0c83ff..ff21a7ab 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -1,11 +1,12 @@ import frappe from frappe.utils import getdate -from lms.lms.utils import has_course_moderator_role +from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role def get_context(context): context.no_cache = 1 context.is_moderator = has_course_moderator_role() + context.is_evaluator = has_course_evaluator_role() classes = frappe.get_all( "LMS Class", fields=[ @@ -15,6 +16,8 @@ def get_context(context): "start_date", "end_date", "paid_class", + "amount", + "currency", "seat_count", ], ) diff --git a/lms/www/courses/course.html b/lms/www/courses/course.html index cd294edd..b8f5b7fe 100644 --- a/lms/www/courses/course.html +++ b/lms/www/courses/course.html @@ -10,8 +10,8 @@ {{ CourseHomeHeader(course) }}
+ {{ CourseHeaderOverlay(course) }}
- {{ CourseHeaderOverlay(course) }} {{ 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) %} @@ -41,7 +41,7 @@ {% macro BreadCrumb(course) %} {% endmacro %} @@ -57,8 +57,8 @@ {% endfor %}
-
- {% if course.title %} {{ course.title }} {% endif %} +
+ {{ course.title }}
@@ -228,7 +228,7 @@ {% elif course.paid_course and not is_instructor %} - + {{ _("Buy This Course") }} diff --git a/lms/www/utils.py b/lms/www/utils.py index a99046bc..33567c75 100644 --- a/lms/www/utils.py +++ b/lms/www/utils.py @@ -128,3 +128,16 @@ def get_quiz_details(assessment, member): existing_submission[0].name if len(existing_submission) else "new-submission" ) assessment.url = f"/quiz-submission/{assessment.assessment_name}/{submission_name}" + + +def is_student(class_name, member=None): + if not member: + member = frappe.session.user + + return frappe.db.exists( + "Class Student", + { + "student": member, + "parent": class_name, + }, + ) From 47c19b4e3d31237b5c515c430ff5fcb4b5067c8f Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 23 Aug 2023 13:00:54 +0530 Subject: [PATCH 03/15] feat: gst fields in class student --- .../doctype/class_student/class_student.json | 57 ++++++++++++++++++- lms/lms/doctype/lms_class/lms_class.py | 44 ++++++++++++++ lms/lms/utils.py | 2 +- lms/public/js/common_functions.js | 12 ++-- lms/www/billing/billing.html | 9 +-- lms/www/billing/billing.js | 13 +++++ lms/www/classes/class.html | 18 ------ lms/www/classes/class.js | 11 +++- lms/www/classes/class_details.html | 24 ++++---- lms/www/classes/class_details.py | 35 +++++++++++- lms/www/classes/index.html | 8 +-- 11 files changed, 175 insertions(+), 58 deletions(-) diff --git a/lms/lms/doctype/class_student/class_student.json b/lms/lms/doctype/class_student/class_student.json index 55017ec0..819050fc 100644 --- a/lms/lms/doctype/class_student/class_student.json +++ b/lms/lms/doctype/class_student/class_student.json @@ -7,16 +7,25 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "student_details_section", "student", + "column_break_oduu", "student_name", "username", - "address", + "payment_details_section", "column_break_zvlp", "amount", "currency", + "column_break_clem", "order_id", "payment_id", - "payment_received" + "payment_received", + "address_details_section", + "address", + "pan", + "column_break_rqoj", + "gstin", + "gst_category" ], "fields": [ { @@ -78,12 +87,54 @@ "fieldname": "payment_received", "fieldtype": "Check", "label": "Payment Received" + }, + { + "fieldname": "student_details_section", + "fieldtype": "Section Break", + "label": "Student Details" + }, + { + "fieldname": "column_break_oduu", + "fieldtype": "Column Break" + }, + { + "fieldname": "payment_details_section", + "fieldtype": "Section Break", + "label": "Payment Details" + }, + { + "fieldname": "column_break_clem", + "fieldtype": "Column Break" + }, + { + "fieldname": "address_details_section", + "fieldtype": "Section Break", + "label": "Address Details" + }, + { + "fieldname": "pan", + "fieldtype": "Data", + "label": "PAN" + }, + { + "fieldname": "column_break_rqoj", + "fieldtype": "Column Break" + }, + { + "fieldname": "gstin", + "fieldtype": "Data", + "label": "GSTIN" + }, + { + "fieldname": "gst_category", + "fieldtype": "Select", + "label": "GST Category" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-22 10:41:40.577437", + "modified": "2023-08-22 21:59:16.678547", "modified_by": "Administrator", "module": "LMS", "name": "Class Student", diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 31db3d95..f7457ecd 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -185,6 +185,50 @@ def authenticate(): return response.json()["access_token"] +@frappe.whitelist() +def create_class( + title, + start_date, + end_date, + description=None, + prerequisite=None, + seat_count=0, + start_time=None, + end_time=None, + medium="Online", + category=None, + paid_class=0, + amount=0, + currency=None, + name=None, +): + frappe.only_for("Moderator") + if name: + class_details = frappe.get_doc("LMS Class", name) + else: + class_details = frappe.get_doc({"doctype": "LMS Class"}) + + class_details.update( + { + "title": title, + "start_date": start_date, + "end_date": end_date, + "description": description, + "prerequisite": prerequisite, + "seat_count": seat_count, + "start_time": start_time, + "end_time": end_time, + "medium": medium, + "category": category, + "paid_class": paid_class, + "amount": amount, + "currency": currency, + } + ) + class_details.save() + return class_details + + @frappe.whitelist() def fetch_lessons(courses): lessons = [] diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 72832ad9..2385625b 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -987,5 +987,5 @@ def add_student_to_class(address, response, classname, client): return f"/classes/{classname}" except Exception as e: frappe.throw( - _("Error during payment: {0}. Please contact the Administrator.").format(e) + _("Error during payment: {0} Please contact the Administrator.").format(e) ) diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index 39adf583..b70e26f7 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -354,6 +354,7 @@ const open_class_dialog = () => { default: class_info && class_info.currency, mandatory_depends_on: "paid_class", depends_on: "paid_class", + only_select: 1, }, ], primary_action_label: __("Save"), @@ -365,20 +366,15 @@ const open_class_dialog = () => { }; const save_class = (values) => { - let method, args; + let args = {}; if (class_info) { - method = "frappe.client.save"; args = Object.assign(class_info, values); } else { - method = "frappe.client.insert"; args = values; - args.doctype = "LMS Class"; } frappe.call({ - method: method, - args: { - doc: args, - }, + method: "lms.lms.doctype.lms_class.lms_class.create_class", + args: args, callback: (r) => { if (r.message) { frappe.show_alert({ diff --git a/lms/www/billing/billing.html b/lms/www/billing/billing.html index 4229d863..56e693e1 100644 --- a/lms/www/billing/billing.html +++ b/lms/www/billing/billing.html @@ -20,7 +20,7 @@ {{ _("Order Details") }}
- {{ _("Enter the billing information and complete the payment to purchase this {0}.").format(module) }} + {{ _("Enter the billing information to complete the payment.").format(module) }}
{% endmacro %} @@ -59,11 +59,4 @@ {%- block script %} {{ super() }} - {% endblock %} diff --git a/lms/www/billing/billing.js b/lms/www/billing/billing.js index cc0c6413..9ff7480f 100644 --- a/lms/www/billing/billing.js +++ b/lms/www/billing/billing.js @@ -44,6 +44,7 @@ const setup_billing = () => { fieldname: "country", options: "Country", reqd: 1, + only_select: 1, }, { fieldtype: "Data", @@ -57,6 +58,18 @@ const setup_billing = () => { fieldname: "phone", reqd: 1, }, + { + fieldtype: "Data", + fieldname: "gstin", + label: __("GSTIN"), + depends_on: (doc) => console.log(doc.country), + }, + { + fieldtype: "Data", + fieldname: "pan", + label: __("PAN"), + depends_on: (doc) => console.log(doc.country), + }, ], body: $("#billing-form").get(0), }); diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index a0a88962..1828fe50 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -600,26 +600,8 @@ {%- block script %} {{ super() }} - {% if is_moderator %} - {% else %} - - {% endif %} - {% endblock %} \ No newline at end of file diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 2e629b5c..7ed03667 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -1,11 +1,13 @@ frappe.ready(() => { let self = this; + frappe.require("controls.bundle.js"); if ($("#live-class-form").length) { - frappe.require("controls.bundle.js", () => { + setTimeout(() => { make_live_class_form(); - }); + }, 1000); } + $(".btn-add-student").click((e) => { show_student_modal(e); }); @@ -308,12 +310,14 @@ const show_course_modal = () => { label: __("Course"), fieldname: "course", reqd: 1, + only_select: 1, }, { fieldtype: "Link", options: "Course Evaluator", label: __("Course Evaluator"), fieldname: "evaluator", + only_select: 1, }, ], primary_action_label: __("Add"), @@ -385,6 +389,7 @@ const show_student_modal = () => { label: __("Student"), fieldname: "student", reqd: 1, + only_select: 1, filters: { ignore_user_type: 1, }, @@ -463,6 +468,7 @@ const show_assessment_modal = (e) => { label: __("Assessment Type"), fieldname: "assessment_type", reqd: 1, + only_select: 1, filters: { name: ["in", ["LMS Assignment", "LMS Quiz"]], }, @@ -474,6 +480,7 @@ const show_assessment_modal = (e) => { label: __("Assessment"), fieldname: "assessment_name", reqd: 1, + only_select: 1, }, { fieldtype: "Section Break", diff --git a/lms/www/classes/class_details.html b/lms/www/classes/class_details.html index 713612cf..dff32a70 100644 --- a/lms/www/classes/class_details.html +++ b/lms/www/classes/class_details.html @@ -8,10 +8,10 @@
{{ ClassHeader(class_info) }}
- {{ CourseHeaderOverlay(class_info) }} + {{ CourseHeaderOverlay(class_info, courses, students) }}
{{ Prerequisites(class_info) }} - {{ CourseList(class_info) }} + {{ CourseList(courses) }}
@@ -22,7 +22,7 @@
{{ BreadCrumb(class_info) }} - {{ ClassHeaderDetails(class_info) }} + {{ ClassHeaderDetails(class_info, courses, students) }}
@@ -40,15 +40,15 @@ {% endmacro %} -{% macro ClassHeaderDetails(class_info) %} +{% macro ClassHeaderDetails(class_info, courses, students) %}
- {{ class_info.courses | length }} {{ _("Courses") }} + {{ courses | length }} {{ _("Courses") }} · - {{ class_info.students | length }} {{ _("Students") }} + {{ students | length }} {{ _("Students") }}
@@ -88,7 +88,7 @@
{% endmacro %} -{% macro CourseHeaderOverlay(class_info) %} +{% macro CourseHeaderOverlay(class_info, courses, students) %}
@@ -102,14 +102,14 @@ - {{ class_info.courses | length }} {{ _("Courses") }} + {{ courses | length }} {{ _("Courses") }}
- {{ class_info.students | length }} {{ _("Students") }} + {{ students | length }} {{ _("Students") }}
@@ -177,14 +177,14 @@ {% endmacro %} -{% macro CourseList(class_info) %} +{% macro CourseList(courses) %}
{{ _("Courses") }}
- {% if class_info.courses | length %} + {% if courses | length %}
- {% for course in class_info.courses %} + {% for course in courses %}
{{ widgets.CourseCard(course=course, read_only=False) }} {% endif %} - {{ CreateLiveClass(class_info) }} - {{ LiveClassList(class_info, live_classes) }} + {{ CreateLiveClass(batch_info) }} + {{ LiveClassList(batch_info, live_classes) }}
{% endmacro %} -{% macro CreateLiveClass(class_info) %} +{% macro CreateLiveClass(batch_info) %} {% if is_moderator %}