chore: resolved conflicts
This commit is contained in:
@@ -1384,6 +1384,7 @@ def save_role(user, role, value):
|
||||
doc.save(ignore_permissions=True)
|
||||
else:
|
||||
frappe.db.delete("Has Role", {"parent": user, "role": role})
|
||||
frappe.clear_cache(user=user)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
0
lms/lms/doctype/lms_coupon/__init__.py
Normal file
0
lms/lms/doctype/lms_coupon/__init__.py
Normal file
8
lms/lms/doctype/lms_coupon/lms_coupon.js
Normal file
8
lms/lms/doctype/lms_coupon/lms_coupon.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("LMS Coupon", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
190
lms/lms/doctype/lms_coupon/lms_coupon.json
Normal file
190
lms/lms/doctype/lms_coupon/lms_coupon.json
Normal file
@@ -0,0 +1,190 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2025-10-11 21:39:11.456420",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"section_break_spfj",
|
||||
"code",
|
||||
"expires_on",
|
||||
"column_break_mptc",
|
||||
"discount_type",
|
||||
"percentage_discount",
|
||||
"fixed_amount_discount",
|
||||
"section_break_ixxu",
|
||||
"usage_limit",
|
||||
"column_break_dcvj",
|
||||
"redemption_count",
|
||||
"section_break_ophm",
|
||||
"applicable_items"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Code",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nFixed Amount",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "expires_on",
|
||||
"fieldtype": "Date",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Expires On"
|
||||
},
|
||||
{
|
||||
"fieldname": "usage_limit",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Usage Limit"
|
||||
},
|
||||
{
|
||||
"fieldname": "applicable_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Applicable Items",
|
||||
"options": "LMS Coupon Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_mptc",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ixxu",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_dcvj",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ophm",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.discount_type=='Percentage'",
|
||||
"fieldname": "percentage_discount",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Percentage Discount",
|
||||
"mandatory_depends_on": "eval:doc.discount_type=='Percentage'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.discount_type=='Fixed Amount'",
|
||||
"fieldname": "fixed_amount_discount",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Fixed Amount Discount",
|
||||
"mandatory_depends_on": "eval:doc.discount_type=='Fixed Amount'"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "redemption_count",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Redemption Count",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_spfj",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-27 19:52:11.835042",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Coupon",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Batch Evaluator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Course Creator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "code"
|
||||
}
|
||||
31
lms/lms/doctype/lms_coupon/lms_coupon.py
Normal file
31
lms/lms/doctype/lms_coupon/lms_coupon.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2025, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, nowdate
|
||||
|
||||
|
||||
class LMSCoupon(Document):
|
||||
def validate(self):
|
||||
self.convert_to_uppercase()
|
||||
self.validate_expiry_date()
|
||||
self.validate_applicable_items()
|
||||
self.validate_usage_limit()
|
||||
|
||||
def convert_to_uppercase(self):
|
||||
if self.code:
|
||||
self.code = self.code.strip().upper()
|
||||
|
||||
def validate_expiry_date(self):
|
||||
if self.expires_on and str(self.expires_on) < nowdate():
|
||||
frappe.throw(_("Expiry date cannot be in the past"))
|
||||
|
||||
def validate_applicable_items(self):
|
||||
if not self.get("applicable_items") or len(self.get("applicable_items")) == 0:
|
||||
frappe.throw(_("At least one applicable item is required"))
|
||||
|
||||
def validate_usage_limit(self):
|
||||
if self.usage_limit is not None and cint(self.usage_limit) < 0:
|
||||
frappe.throw(_("Usage limit cannot be negative"))
|
||||
20
lms/lms/doctype/lms_coupon/test_lms_coupon.py
Normal file
20
lms/lms/doctype/lms_coupon/test_lms_coupon.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestLMSCoupon(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for LMSCoupon.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
0
lms/lms/doctype/lms_coupon_item/__init__.py
Normal file
0
lms/lms/doctype/lms_coupon_item/__init__.py
Normal file
43
lms/lms/doctype/lms_coupon_item/lms_coupon_item.json
Normal file
43
lms/lms/doctype/lms_coupon_item/lms_coupon_item.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-10-11 21:45:00",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_doctype",
|
||||
"reference_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference DocType",
|
||||
"options": "\nLMS Course\nLMS Batch",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"options": "reference_doctype",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-12 17:27:14.123811",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Coupon Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
9
lms/lms/doctype/lms_coupon_item/lms_coupon_item.py
Normal file
9
lms/lms/doctype/lms_coupon_item/lms_coupon_item.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2025, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSCouponItem(Document):
|
||||
pass
|
||||
@@ -16,14 +16,18 @@
|
||||
"payment_received",
|
||||
"payment_for_certificate",
|
||||
"payment_details_section",
|
||||
"currency",
|
||||
"original_amount",
|
||||
"discount_amount",
|
||||
"amount",
|
||||
"amount_with_gst",
|
||||
"column_break_yxpl",
|
||||
"order_id",
|
||||
"payment_id",
|
||||
"currency",
|
||||
"coupon",
|
||||
"coupon_code",
|
||||
"billing_details_section",
|
||||
"address",
|
||||
"payment_id",
|
||||
"order_id",
|
||||
"column_break_monu",
|
||||
"gstin",
|
||||
"pan"
|
||||
@@ -47,6 +51,19 @@
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "coupon",
|
||||
"fieldtype": "Link",
|
||||
"label": "Coupon",
|
||||
"options": "LMS Coupon"
|
||||
},
|
||||
{
|
||||
"depends_on": "coupon",
|
||||
"fieldname": "discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
@@ -117,12 +134,6 @@
|
||||
"options": "User",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.currency == \"INR\";",
|
||||
"fieldname": "amount_with_gst",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount with GST"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_for_document_type",
|
||||
"fieldtype": "Select",
|
||||
@@ -149,6 +160,27 @@
|
||||
"fieldtype": "Check",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Payment for Certificate"
|
||||
},
|
||||
{
|
||||
"depends_on": "coupon",
|
||||
"fetch_from": "coupon.code",
|
||||
"fieldname": "coupon_code",
|
||||
"fieldtype": "Data",
|
||||
"label": "Coupon Code",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.currency == \"INR\"",
|
||||
"fieldname": "amount_with_gst",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount with GST"
|
||||
},
|
||||
{
|
||||
"depends_on": "coupon",
|
||||
"fieldname": "original_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Original Amount",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -162,7 +194,7 @@
|
||||
"link_fieldname": "payment"
|
||||
}
|
||||
],
|
||||
"modified": "2025-09-23 11:04:00.462274",
|
||||
"modified": "2025-11-12 12:39:52.466297",
|
||||
"modified_by": "sayali@frappe.io",
|
||||
"module": "LMS",
|
||||
"name": "LMS Payment",
|
||||
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.email.doctype.email_template.email_template import get_email_template
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, nowdate
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
|
||||
|
||||
class LMSPayment(Document):
|
||||
|
||||
@@ -23,23 +23,38 @@ def get_payment_link(
|
||||
docname,
|
||||
title,
|
||||
amount,
|
||||
total_amount,
|
||||
discount_amount,
|
||||
gst_amount,
|
||||
currency,
|
||||
address,
|
||||
redirect_to,
|
||||
payment_for_certificate,
|
||||
coupon_code=None,
|
||||
coupon=None,
|
||||
):
|
||||
payment_gateway = get_payment_gateway()
|
||||
address = frappe._dict(address)
|
||||
amount_with_gst = total_amount if total_amount != amount else 0
|
||||
original_amount = amount
|
||||
amount -= discount_amount
|
||||
amount_with_gst = get_amount_with_gst(amount, gst_amount)
|
||||
|
||||
payment = record_payment(
|
||||
address, doctype, docname, amount, currency, amount_with_gst, payment_for_certificate
|
||||
address,
|
||||
doctype,
|
||||
docname,
|
||||
amount,
|
||||
original_amount,
|
||||
currency,
|
||||
amount_with_gst,
|
||||
discount_amount,
|
||||
payment_for_certificate,
|
||||
coupon_code,
|
||||
coupon,
|
||||
)
|
||||
controller = get_controller(payment_gateway)
|
||||
|
||||
payment_details = {
|
||||
"amount": total_amount,
|
||||
"amount": amount_with_gst if amount_with_gst else amount,
|
||||
"title": f"Payment for {doctype} {title} {docname}",
|
||||
"description": f"{address.billing_name}'s payment for {title}",
|
||||
"reference_doctype": doctype,
|
||||
@@ -51,23 +66,41 @@ def get_payment_link(
|
||||
"redirect_to": redirect_to,
|
||||
"payment": payment.name,
|
||||
}
|
||||
if payment_gateway == "Razorpay":
|
||||
order = controller.create_order(**payment_details)
|
||||
payment_details.update({"order_id": order.get("id")})
|
||||
|
||||
create_order(payment_gateway, payment_details, controller)
|
||||
url = controller.get_payment_url(**payment_details)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def create_order(payment_gateway, payment_details, controller):
|
||||
if payment_gateway != "Razorpay":
|
||||
return
|
||||
|
||||
order = controller.create_order(**payment_details)
|
||||
payment_details.update({"order_id": order.get("id")})
|
||||
|
||||
|
||||
def get_amount_with_gst(amount, gst_amount):
|
||||
amount_with_gst = 0
|
||||
if gst_amount:
|
||||
amount_with_gst = amount + gst_amount
|
||||
|
||||
return amount_with_gst
|
||||
|
||||
|
||||
def record_payment(
|
||||
address,
|
||||
doctype,
|
||||
docname,
|
||||
amount,
|
||||
original_amount,
|
||||
currency,
|
||||
amount_with_gst=0,
|
||||
discount_amount=0,
|
||||
payment_for_certificate=0,
|
||||
coupon_code=None,
|
||||
coupon=None,
|
||||
):
|
||||
address = frappe._dict(address)
|
||||
address_name = save_address(address)
|
||||
@@ -80,6 +113,7 @@ def record_payment(
|
||||
"address": address_name,
|
||||
"amount": amount,
|
||||
"currency": currency,
|
||||
"discount_amount": discount_amount,
|
||||
"amount_with_gst": amount_with_gst,
|
||||
"gstin": address.gstin,
|
||||
"pan": address.pan,
|
||||
@@ -89,6 +123,16 @@ def record_payment(
|
||||
"payment_for_certificate": payment_for_certificate,
|
||||
}
|
||||
)
|
||||
if coupon_code:
|
||||
payment_doc.update(
|
||||
{
|
||||
"coupon": coupon,
|
||||
"coupon_code": coupon_code,
|
||||
"discount_amount": discount_amount,
|
||||
"original_amount": original_amount,
|
||||
}
|
||||
)
|
||||
|
||||
payment_doc.save(ignore_permissions=True)
|
||||
return payment_doc
|
||||
|
||||
|
||||
349
lms/lms/utils.py
349
lms/lms/utils.py
@@ -1587,6 +1587,28 @@ def get_batch_students(batch):
|
||||
"LMS Batch Enrollment", filters={"batch": batch}, fields=["member", "name"]
|
||||
)
|
||||
|
||||
for student in students_list:
|
||||
details = get_batch_student_details(student)
|
||||
calculate_student_progress(batch, details)
|
||||
students.append(details)
|
||||
students = sorted(students, key=lambda x: x.progress, reverse=True)
|
||||
return students
|
||||
|
||||
|
||||
def get_batch_student_details(student):
|
||||
details = frappe.db.get_value(
|
||||
"User",
|
||||
student.member,
|
||||
["full_name", "email", "username", "last_active", "user_image"],
|
||||
as_dict=True,
|
||||
)
|
||||
details.last_active = format_datetime(details.last_active, "dd MMM YY")
|
||||
details.name = student.name
|
||||
details.assessments = frappe._dict()
|
||||
return details
|
||||
|
||||
|
||||
def calculate_student_progress(batch, details):
|
||||
batch_courses = frappe.get_all("Batch Course", {"parent": batch}, ["course", "title"])
|
||||
assessments = frappe.get_all(
|
||||
"LMS Assessment",
|
||||
@@ -1594,53 +1616,55 @@ def get_batch_students(batch):
|
||||
fields=["name", "assessment_type", "assessment_name"],
|
||||
)
|
||||
|
||||
for student in students_list:
|
||||
courses_completed = 0
|
||||
assessments_completed = 0
|
||||
detail = frappe.db.get_value(
|
||||
"User",
|
||||
student.member,
|
||||
["full_name", "email", "username", "last_active", "user_image"],
|
||||
as_dict=True,
|
||||
calculate_course_progress(batch_courses, details)
|
||||
calculate_assessment_progress(assessments, details)
|
||||
|
||||
if len(batch_courses) + len(assessments):
|
||||
details.progress = flt(
|
||||
(
|
||||
(details.average_course_progress * len(batch_courses))
|
||||
+ (details.average_assessments_progress * len(assessments))
|
||||
)
|
||||
/ (len(batch_courses) + len(assessments)),
|
||||
2,
|
||||
)
|
||||
detail.last_active = format_datetime(detail.last_active, "dd MMM YY")
|
||||
detail.name = student.name
|
||||
detail.courses = frappe._dict()
|
||||
detail.assessments = frappe._dict()
|
||||
else:
|
||||
details.progress = 0
|
||||
|
||||
""" Iterate through courses and track their progress """
|
||||
for course in batch_courses:
|
||||
progress = frappe.db.get_value(
|
||||
"LMS Enrollment", {"course": course.course, "member": student.member}, "progress"
|
||||
)
|
||||
detail.courses[course.title] = progress
|
||||
if progress == 100:
|
||||
courses_completed += 1
|
||||
|
||||
""" Iterate through assessments and track their progress """
|
||||
for assessment in assessments:
|
||||
title = frappe.db.get_value(assessment.assessment_type, assessment.assessment_name, "title")
|
||||
assessment_info = has_submitted_assessment(
|
||||
assessment.assessment_name, assessment.assessment_type, student.member
|
||||
)
|
||||
detail.assessments[title] = assessment_info
|
||||
def calculate_course_progress(batch_courses, details):
|
||||
course_progress = []
|
||||
details.courses = frappe._dict()
|
||||
|
||||
if assessment_info.result == "Pass":
|
||||
assessments_completed += 1
|
||||
for course in batch_courses:
|
||||
progress = frappe.db.get_value(
|
||||
"LMS Enrollment", {"course": course.course, "member": details.email}, "progress"
|
||||
)
|
||||
details.courses[course.title] = progress
|
||||
course_progress.append(progress)
|
||||
|
||||
detail.courses_completed = courses_completed
|
||||
detail.assessments_completed = assessments_completed
|
||||
if len(batch_courses) + len(assessments):
|
||||
detail.progress = flt(
|
||||
((courses_completed + assessments_completed) / (len(batch_courses) + len(assessments)) * 100),
|
||||
2,
|
||||
)
|
||||
else:
|
||||
detail.progress = 0
|
||||
details.average_course_progress = (
|
||||
flt(sum(course_progress) / len(batch_courses), 2) if len(batch_courses) else 0
|
||||
)
|
||||
|
||||
students.append(detail)
|
||||
students = sorted(students, key=lambda x: x.progress, reverse=True)
|
||||
return students
|
||||
|
||||
def calculate_assessment_progress(assessments, details):
|
||||
assessments_completed = 0
|
||||
details.assessments = frappe._dict()
|
||||
|
||||
for assessment in assessments:
|
||||
title = frappe.db.get_value(assessment.assessment_type, assessment.assessment_name, "title")
|
||||
assessment_info = has_submitted_assessment(
|
||||
assessment.assessment_name, assessment.assessment_type, details.email
|
||||
)
|
||||
details.assessments[title] = assessment_info
|
||||
|
||||
if assessment_info.result == "Pass":
|
||||
assessments_completed += 1
|
||||
|
||||
details.average_assessments_progress = (
|
||||
flt((assessments_completed / len(assessments) * 100), 2) if len(assessments) else 0
|
||||
)
|
||||
|
||||
|
||||
def has_submitted_assessment(assessment, assessment_type, member=None):
|
||||
@@ -1752,51 +1776,140 @@ def get_discussion_replies(topic):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_order_summary(doctype, docname, country=None):
|
||||
if doctype == "LMS Course":
|
||||
details = frappe.db.get_value(
|
||||
"LMS Course",
|
||||
docname,
|
||||
[
|
||||
"title",
|
||||
"name",
|
||||
"paid_course",
|
||||
"paid_certificate",
|
||||
"course_price as amount",
|
||||
"currency",
|
||||
"amount_usd",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if not details.paid_course and not details.paid_certificate:
|
||||
raise frappe.throw(_("This course is free."))
|
||||
|
||||
else:
|
||||
details = frappe.db.get_value(
|
||||
"LMS Batch",
|
||||
docname,
|
||||
["title", "name", "paid_batch", "amount", "currency", "amount_usd"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if not details.paid_batch:
|
||||
raise frappe.throw(_("To join this batch, please contact the Administrator."))
|
||||
def get_order_summary(doctype, docname, coupon=None, country=None):
|
||||
details = get_paid_course_details(docname) if doctype == "LMS Course" else get_paid_batch_details(docname)
|
||||
|
||||
details.amount, details.currency = check_multicurrency(
|
||||
details.amount, details.currency, country, details.amount_usd
|
||||
)
|
||||
|
||||
details.original_amount = details.amount
|
||||
details.original_amount_formatted = fmt_money(details.amount, 0, details.currency)
|
||||
|
||||
if details.currency == "INR":
|
||||
details.amount, details.gst_applied = apply_gst(details.amount, country)
|
||||
details.gst_amount_formatted = fmt_money(details.gst_applied, 0, details.currency)
|
||||
adjust_amount_for_coupon(details, coupon, doctype, docname)
|
||||
get_gst_details(details, country)
|
||||
|
||||
details.total_amount = details.amount
|
||||
details.total_amount_formatted = fmt_money(details.amount, 0, details.currency)
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def get_paid_course_details(docname):
|
||||
details = frappe.db.get_value(
|
||||
"LMS Course",
|
||||
docname,
|
||||
[
|
||||
"title",
|
||||
"name",
|
||||
"paid_course",
|
||||
"paid_certificate",
|
||||
"course_price as amount",
|
||||
"currency",
|
||||
"amount_usd",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if not details.paid_course and not details.paid_certificate:
|
||||
raise frappe.throw(_("This course is free."))
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def get_paid_batch_details(docname):
|
||||
details = frappe.db.get_value(
|
||||
"LMS Batch",
|
||||
docname,
|
||||
["title", "name", "paid_batch", "amount", "currency", "amount_usd"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if not details.paid_batch:
|
||||
raise frappe.throw(_("To join this batch, please contact the Administrator."))
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def adjust_amount_for_coupon(details, coupon, doctype, docname):
|
||||
if not coupon:
|
||||
return
|
||||
discount_amount, subtotal, coupon_name = apply_coupon(doctype, docname, coupon, details.amount)
|
||||
details.amount = subtotal
|
||||
details.discount_amount = discount_amount
|
||||
details.discount_amount_formatted = fmt_money(discount_amount, 0, details.currency)
|
||||
details.coupon = coupon_name
|
||||
|
||||
|
||||
def get_gst_details(details, country):
|
||||
if details.currency != "INR":
|
||||
return
|
||||
|
||||
details.amount, details.gst_applied = apply_gst(details.amount, country)
|
||||
details.gst_amount_formatted = fmt_money(details.gst_applied, 0, details.currency)
|
||||
|
||||
|
||||
def apply_coupon(doctype, docname, code, base_amount):
|
||||
coupon_name = frappe.db.exists("LMS Coupon", {"code": code, "enabled": 1})
|
||||
if not coupon_name:
|
||||
frappe.throw(_("The coupon code '{0}' is invalid.").format(code))
|
||||
|
||||
coupon = frappe.db.get_value(
|
||||
"LMS Coupon",
|
||||
coupon_name,
|
||||
[
|
||||
"expires_on",
|
||||
"usage_limit",
|
||||
"redemption_count",
|
||||
"discount_type",
|
||||
"percentage_discount",
|
||||
"fixed_amount_discount",
|
||||
"name",
|
||||
"code",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
validate_coupon(code, coupon)
|
||||
validate_coupon_applicability(doctype, docname, coupon_name)
|
||||
|
||||
discount_amount = calculate_discount_amount(base_amount, coupon)
|
||||
subtotal = max(flt(base_amount) - flt(discount_amount), 0)
|
||||
|
||||
return discount_amount, subtotal, coupon_name
|
||||
|
||||
|
||||
def validate_coupon(code, coupon):
|
||||
if coupon.expires_on and getdate(coupon.expires_on) < getdate():
|
||||
frappe.throw(_("This coupon has expired."))
|
||||
|
||||
if coupon.usage_limit and cint(coupon.redemption_count) >= cint(coupon.usage_limit):
|
||||
frappe.throw(_("This coupon has reached its maximum usage limit."))
|
||||
|
||||
|
||||
def validate_coupon_applicability(doctype, docname, coupon_name):
|
||||
applicable_item = frappe.db.exists(
|
||||
"LMS Coupon Item", {"parent": coupon_name, "reference_doctype": doctype, "reference_name": docname}
|
||||
)
|
||||
if not applicable_item:
|
||||
frappe.throw(
|
||||
_("This coupon is not applicable to this {0}.").format(
|
||||
"Course" if doctype == "LMS Course" else "Batch"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def calculate_discount_amount(base_amount, coupon):
|
||||
discount_amount = 0
|
||||
|
||||
if coupon.discount_type == "Percentage":
|
||||
discount_amount = (base_amount * coupon.percentage_discount) / 100
|
||||
elif coupon.discount_type == "Fixed Amount":
|
||||
discount_amount = base_amount - coupon.fixed_amount_discount
|
||||
|
||||
return discount_amount
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_lesson_creation_details(course, chapter, lesson):
|
||||
chapter_name = frappe.db.get_value("Chapter Reference", {"parent": course, "idx": chapter}, "chapter")
|
||||
@@ -1843,49 +1956,79 @@ def publish_notifications(doc, method):
|
||||
|
||||
|
||||
def update_payment_record(doctype, docname):
|
||||
request = frappe.get_all(
|
||||
request = get_integration_requests(doctype, docname)
|
||||
|
||||
if len(request):
|
||||
data = request[0].data
|
||||
data = frappe._dict(json.loads(data))
|
||||
payment_doc = get_payment_doc(data.payment)
|
||||
|
||||
update_payment_details(data)
|
||||
update_coupon_redemption(payment_doc)
|
||||
|
||||
if payment_doc.payment_for_certificate:
|
||||
update_certificate_purchase(docname, data.payment)
|
||||
elif doctype == "LMS Course":
|
||||
enroll_in_course(docname, data.payment)
|
||||
else:
|
||||
enroll_in_batch(docname, data.payment)
|
||||
|
||||
|
||||
def get_integration_requests(doctype, docname):
|
||||
return frappe.get_all(
|
||||
"Integration Request",
|
||||
{
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname,
|
||||
"owner": frappe.session.user,
|
||||
},
|
||||
["data"],
|
||||
order_by="creation desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if len(request):
|
||||
data = frappe.db.get_value("Integration Request", request[0].name, "data")
|
||||
data = frappe._dict(json.loads(data))
|
||||
|
||||
payment_gateway = data.get("payment_gateway")
|
||||
if payment_gateway == "Razorpay":
|
||||
payment_id = "razorpay_payment_id"
|
||||
elif "Stripe" in payment_gateway:
|
||||
payment_id = "stripe_token_id"
|
||||
else:
|
||||
payment_id = "order_id"
|
||||
def get_payment_doc(payment_name):
|
||||
return frappe.db.get_value(
|
||||
"LMS Payment", payment_name, ["name", "coupon", "payment_for_certificate"], as_dict=True
|
||||
)
|
||||
|
||||
|
||||
def update_payment_details(data):
|
||||
payment_id = get_payment_id(data)
|
||||
|
||||
frappe.db.set_value(
|
||||
"LMS Payment",
|
||||
data.payment,
|
||||
{
|
||||
"payment_received": 1,
|
||||
"payment_id": data.get(payment_id),
|
||||
"order_id": data.get("order_id"),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_payment_id(data):
|
||||
payment_gateway = data.get("payment_gateway")
|
||||
if payment_gateway == "Razorpay":
|
||||
payment_id = "razorpay_payment_id"
|
||||
elif "Stripe" in payment_gateway:
|
||||
payment_id = "stripe_token_id"
|
||||
else:
|
||||
payment_id = "order_id"
|
||||
return payment_id
|
||||
|
||||
|
||||
def update_coupon_redemption(payment_doc):
|
||||
if payment_doc.coupon:
|
||||
redemption_count = frappe.db.get_value("LMS Coupon", payment_doc.coupon, "redemption_count") or 0
|
||||
|
||||
frappe.db.set_value(
|
||||
"LMS Payment",
|
||||
data.payment,
|
||||
{
|
||||
"payment_received": 1,
|
||||
"payment_id": data.get(payment_id),
|
||||
"order_id": data.get("order_id"),
|
||||
},
|
||||
"LMS Coupon",
|
||||
payment_doc.coupon,
|
||||
"redemption_count",
|
||||
redemption_count + 1,
|
||||
)
|
||||
payment_for_certificate = frappe.db.get_value("LMS Payment", data.payment, "payment_for_certificate")
|
||||
|
||||
try:
|
||||
if payment_for_certificate:
|
||||
update_certificate_purchase(docname, data.payment)
|
||||
elif doctype == "LMS Course":
|
||||
enroll_in_course(docname, data.payment)
|
||||
else:
|
||||
enroll_in_batch(docname, data.payment)
|
||||
except Exception as e:
|
||||
frappe.log_error(frappe.get_traceback(), _("Enrollment Failed, {0}").format(e))
|
||||
|
||||
|
||||
def enroll_in_course(course, payment_name):
|
||||
|
||||
Reference in New Issue
Block a user