Merge pull request #619 from pateljannat/state-validation

fix: billing flow
This commit is contained in:
Jannat Patel
2023-09-25 17:57:50 +05:30
committed by GitHub
14 changed files with 190 additions and 32 deletions

View File

@@ -12,7 +12,12 @@ frappe.ui.form.on("LMS Batch", {
}); });
frm.set_query("reference_doctype", "timetable", function () { frm.set_query("reference_doctype", "timetable", function () {
let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"]; let doctypes = [
"Course Lesson",
"LMS Quiz",
"LMS Assignment",
"LMS Live Class",
];
return { return {
filters: { filters: {
name: ["in", doctypes], name: ["in", doctypes],

View File

@@ -146,8 +146,9 @@
}, },
{ {
"fieldname": "category", "fieldname": "category",
"fieldtype": "Autocomplete", "fieldtype": "Link",
"label": "Category" "label": "Category",
"options": "LMS Category"
}, },
{ {
"fieldname": "section_break_ubxi", "fieldname": "section_break_ubxi",
@@ -221,7 +222,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-09-20 11:25:10.683688", "modified": "2023-09-20 14:40:45.940540",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",

View File

@@ -18,6 +18,7 @@ from frappe.utils import (
get_datetime, get_datetime,
getdate, getdate,
validate_phone_number, validate_phone_number,
ceil,
) )
from frappe.utils.dateutils import get_period from frappe.utils.dateutils import get_period
from lms.lms.md import find_macros, markdown_to_html from lms.lms.md import find_macros, markdown_to_html
@@ -844,7 +845,7 @@ def get_payment_options(doctype, docname, phone, country):
validate_phone_number(phone, True) validate_phone_number(phone, True)
details = get_details(doctype, docname) details = get_details(doctype, docname)
details.amount, details.currency = check_multicurrency( details.amount, details.currency = check_multicurrency(
details.amount, details.currency details.amount, details.currency, country
) )
if details.currency == "INR": if details.currency == "INR":
details.amount, details.gst_applied = apply_gst(details.amount, country) details.amount, details.gst_applied = apply_gst(details.amount, country)
@@ -868,18 +869,20 @@ def get_payment_options(doctype, docname, phone, country):
return options return options
def check_multicurrency(amount, currency): def check_multicurrency(amount, currency, country=None):
show_usd_equivalent = frappe.db.get_single_value("LMS Settings", "show_usd_equivalent") show_usd_equivalent = frappe.db.get_single_value("LMS Settings", "show_usd_equivalent")
exception_country = frappe.get_all( exception_country = frappe.get_all(
"Payment Country", filters={"parent": "LMS Settings"}, pluck="country" "Payment Country", filters={"parent": "LMS Settings"}, pluck="country"
) )
apply_rounding = frappe.db.get_single_value("LMS Settings", "apply_rounding") apply_rounding = frappe.db.get_single_value("LMS Settings", "apply_rounding")
country = frappe.db.get_value("User", frappe.session.user, "country") country = country or frappe.db.get_value(
"Address", {"email_id": frappe.session.user}, "country"
)
if not show_usd_equivalent or currency == "USD": if not show_usd_equivalent or currency == "USD":
return amount, currency return amount, currency
if exception_country and country in exception_country: if not country or (exception_country and country in exception_country):
return amount, currency return amount, currency
exchange_rate = get_current_exchange_rate(currency, "USD") exchange_rate = get_current_exchange_rate(currency, "USD")
@@ -887,7 +890,7 @@ def check_multicurrency(amount, currency):
currency = "USD" currency = "USD"
if apply_rounding and amount % 100 != 0: if apply_rounding and amount % 100 != 0:
amount = amount + 100 - amount % 100 amount = ceil(amount + 100 - amount % 100)
return amount, currency return amount, currency
@@ -930,7 +933,15 @@ def get_details(doctype, docname):
def save_address(address): def save_address(address):
address.update( filters = {"email_id": frappe.session.user}
exists = frappe.db.exists("Address", filters)
if exists:
address_doc = frappe.get_last_doc("Address", filters=filters)
else:
address_doc = frappe.new_doc("Address")
address_doc.update(address)
address_doc.update(
{ {
"address_title": frappe.db.get_value("User", frappe.session.user, "full_name"), "address_title": frappe.db.get_value("User", frappe.session.user, "full_name"),
"address_type": "Billing", "address_type": "Billing",
@@ -938,10 +949,8 @@ def save_address(address):
"email_id": frappe.session.user, "email_id": frappe.session.user,
} }
) )
doc = frappe.new_doc("Address") address_doc.save(ignore_permissions=True)
doc.update(address) return address_doc.name
doc.save(ignore_permissions=True)
return doc.name
def get_client(): def get_client():
@@ -1064,3 +1073,10 @@ def get_current_exchange_rate(source, target="USD"):
response = requests.request("GET", url) response = requests.request("GET", url)
details = response.json() details = response.json()
return details["rates"][target] return details["rates"][target]
@frappe.whitelist()
def change_currency(amount, currency, country=None):
amount = cint(amount)
amount, currency = check_multicurrency(amount, currency, country)
return fmt_money(amount, 0, currency)

View File

@@ -2442,4 +2442,15 @@ select {
justify-content: space-between; justify-content: space-between;
width: 50%; width: 50%;
margin: 0 auto 1rem; margin: 0 auto 1rem;
}
.batch-details {
width: 50%;
margin: 2rem 0;
}
@media (max-width: 1000px) {
.batch-details {
width: 100%;
}
} }

View File

@@ -320,6 +320,7 @@ const open_batch_dialog = () => {
label: __("Category"), label: __("Category"),
fieldname: "category", fieldname: "category",
options: "LMS Category", options: "LMS Category",
only_select: 1,
default: batch_info && batch_info.category, default: batch_info && batch_info.category,
}, },
{ {

View File

@@ -1,6 +1,6 @@
from frappe import _ from frappe import _
import frappe import frappe
from frappe.utils import getdate, get_datetime from frappe.utils import getdate
from lms.www.utils import get_assessments, is_student from lms.www.utils import get_assessments, is_student
from lms.lms.utils import ( from lms.lms.utils import (
has_course_moderator_role, has_course_moderator_role,
@@ -52,14 +52,14 @@ def get_context(context):
"Batch Course", "Batch Course",
{"parent": batch_name}, {"parent": batch_name},
["name", "course", "title"], ["name", "course", "title"],
order_by="creation desc", order_by="idx",
) )
batch_students = frappe.get_all( batch_students = frappe.get_all(
"Batch Student", "Batch Student",
{"parent": batch_name}, {"parent": batch_name},
["name", "student", "student_name", "username"], ["name", "student", "student_name", "username"],
order_by="creation desc", order_by="idx",
) )
context.batch_courses = get_class_course_details(batch_courses) context.batch_courses = get_class_course_details(batch_courses)

View File

@@ -164,24 +164,26 @@
{% macro BatchDetails(batch_info) %} {% macro BatchDetails(batch_info) %}
<div class="course-description-section w-50"> <div class="batch-details">
<div class="mt-2"> {{ batch_info.batch_details }}
{{ batch_info.batch_details }}
</div>
</div> </div>
{% endmacro %} {% endmacro %}
{% macro CourseList(courses) %} {% macro CourseList(courses) %}
<div class="batch-course-list"> <div class="batch-course-list">
{% if is_moderator %}
<button class="btn btn-default btn-sm btn-add-course pull-right"> <div class="flex align-center">
{{ _("Add Courses") }} <div class="page-title">
</button> {{ _("Courses") }}
{% endif %} </div>
<div class="page-title"> {% if is_moderator %}
{{ _("Courses") }} <button class="btn btn-default btn-sm btn-add-course ml-4">
{{ _("Add Course") }}
</button>
{% endif %}
</div> </div>
{% if courses | length %} {% if courses | length %}
<div class="cards-parent mt-2"> <div class="cards-parent mt-2">
{% for course in courses %} {% for course in courses %}

View File

@@ -1,6 +1,10 @@
import frappe import frappe
from frappe import _ from frappe import _
from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role from lms.lms.utils import (
has_course_moderator_role,
has_course_evaluator_role,
check_multicurrency,
)
from lms.www.utils import is_student from lms.www.utils import is_student
@@ -29,6 +33,13 @@ def get_context(context):
as_dict=1, as_dict=1,
) )
if context.batch_info.amount and context.batch_info.currency:
amount, currency = check_multicurrency(
context.batch_info.amount, context.batch_info.currency
)
context.batch_info.amount = amount
context.batch_info.currency = currency
context.is_moderator = has_course_moderator_role() context.is_moderator = has_course_moderator_role()
context.is_evaluator = has_course_evaluator_role() context.is_evaluator = has_course_evaluator_role()
context.is_student = is_student(batch_name) context.is_student = is_student(batch_name)
@@ -44,7 +55,7 @@ def get_context(context):
"Batch Course", "Batch Course",
{"parent": batch_name}, {"parent": batch_name},
["name as batch_course", "course", "title", "evaluator"], ["name as batch_course", "course", "title", "evaluator"],
order_by="creation desc", order_by="idx",
) )
for course in context.courses: for course in context.courses:

View File

@@ -1,6 +1,10 @@
import frappe import frappe
from frappe.utils import getdate from frappe.utils import getdate
from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role from lms.lms.utils import (
has_course_moderator_role,
has_course_evaluator_role,
check_multicurrency,
)
def get_context(context): def get_context(context):
@@ -28,6 +32,12 @@ def get_context(context):
for batch in batches: for batch in batches:
batch.student_count = frappe.db.count("Batch Student", {"parent": batch.name}) batch.student_count = frappe.db.count("Batch Student", {"parent": batch.name})
batch.course_count = frappe.db.count("Batch Course", {"parent": batch.name}) batch.course_count = frappe.db.count("Batch Course", {"parent": batch.name})
if batch.amount and batch.currency:
amount, currency = check_multicurrency(batch.amount, batch.currency)
batch.amount = amount
batch.currency = currency
batch.seats_left = ( batch.seats_left = (
batch.seat_count - batch.student_count if batch.seat_count else None batch.seat_count - batch.student_count if batch.seat_count else None
) )

View File

@@ -37,7 +37,8 @@
<div class="flex"> <div class="flex">
<div class="field-label"> <div class="field-label">
{{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }} {{ _("Total Price: ") }}
<span class="total-price">{{ frappe.utils.fmt_money(amount, 2, currency) }}</span>
</div> </div>
</div> </div>
{% if gst_applied %} {% if gst_applied %}
@@ -64,4 +65,11 @@
{%- block script %} {%- block script %}
{{ super() }} {{ super() }}
<script src="https://checkout.razorpay.com/v1/checkout.js"></script> <script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<script>
const address = {{ address if address else 0 }};
const amount = {{ amount }};
const currency = "{{ currency }}";
const exception_country = {{ exception_country }};
const original_price_formatted = "{{ frappe.utils.fmt_money(original_amount, 0, original_currency) }}"
</script>
{% endblock %} {% endblock %}

View File

@@ -18,23 +18,27 @@ const setup_billing = () => {
label: __("Billing Name"), label: __("Billing Name"),
fieldname: "billing_name", fieldname: "billing_name",
reqd: 1, reqd: 1,
default: address && address.billing_name,
}, },
{ {
fieldtype: "Data", fieldtype: "Data",
label: __("Address Line 1"), label: __("Address Line 1"),
fieldname: "address_line1", fieldname: "address_line1",
reqd: 1, reqd: 1,
default: address && address.address_line1,
}, },
{ {
fieldtype: "Data", fieldtype: "Data",
label: __("Address Line 2"), label: __("Address Line 2"),
fieldname: "address_line2", fieldname: "address_line2",
default: address && address.address_line2,
}, },
{ {
fieldtype: "Data", fieldtype: "Data",
label: __("City/Town"), label: __("City/Town"),
fieldname: "city", fieldname: "city",
reqd: 1, reqd: 1,
default: address && address.city,
}, },
{ {
fieldtype: "Column Break", fieldtype: "Column Break",
@@ -43,6 +47,7 @@ const setup_billing = () => {
fieldtype: "Data", fieldtype: "Data",
label: __("State/Province"), label: __("State/Province"),
fieldname: "state", fieldname: "state",
default: address && address.state,
}, },
{ {
fieldtype: "Link", fieldtype: "Link",
@@ -51,18 +56,24 @@ const setup_billing = () => {
options: "Country", options: "Country",
reqd: 1, reqd: 1,
only_select: 1, only_select: 1,
default: address && address.country,
change: () => {
change_currency();
},
}, },
{ {
fieldtype: "Data", fieldtype: "Data",
label: __("Postal Code"), label: __("Postal Code"),
fieldname: "pincode", fieldname: "pincode",
reqd: 1, reqd: 1,
default: address && address.pincode,
}, },
{ {
fieldtype: "Data", fieldtype: "Data",
label: __("Phone Number"), label: __("Phone Number"),
fieldname: "phone", fieldname: "phone",
reqd: 1, reqd: 1,
default: address && address.phone,
}, },
{ {
fieldtype: "Section Break", fieldtype: "Section Break",
@@ -143,3 +154,33 @@ const handle_success = (response, doctype, docname, address, order_id) => {
}, },
}); });
}; };
const change_currency = () => {
let country = this.billing.get_value("country");
if (exception_country.includes(country)) {
update_price(original_price_formatted);
return;
}
frappe.call({
method: "lms.lms.utils.change_currency",
args: {
country: country,
amount: amount,
currency: currency,
},
callback: (data) => {
let current_price = $(".total-price").text();
if (current_price != data.message) {
update_price(data.message);
}
},
});
};
const update_price = (price) => {
$(".total-price").text(price);
frappe.show_alert({
message: "Total Price has been updated.",
indicator: "yellow",
});
};

View File

@@ -14,10 +14,17 @@ def get_context(context):
validate_access(doctype, docname, module) validate_access(doctype, docname, module)
get_billing_details(context) get_billing_details(context)
context.original_amount = context.amount
context.original_currency = context.currency
context.exception_country = frappe.get_all(
"Payment Country", filters={"parent": "LMS Settings"}, pluck="country"
)
context.amount, context.currency = check_multicurrency( context.amount, context.currency = check_multicurrency(
context.amount, context.currency context.amount, context.currency
) )
context.address = get_address()
if context.currency == "INR": if context.currency == "INR":
context.amount, context.gst_applied = apply_gst(context.amount, None) context.amount, context.gst_applied = apply_gst(context.amount, None)
@@ -75,3 +82,35 @@ def get_billing_details(context):
context.title = details.title context.title = details.title
context.amount = details.amount context.amount = details.amount
context.currency = details.currency context.currency = details.currency
def get_address():
address = frappe.get_all(
"Address",
{"email_id": frappe.session.user},
[
"address_title as billing_name",
"address_line1",
"address_line2",
"city",
"state",
"country",
"pincode",
"phone",
],
order_by="creation desc",
limit=1,
)
if not len(address):
return None
else:
address = address[0]
if not address.address_line2:
address.address_line2 = ""
if not address.state:
address.state = ""
return address

View File

@@ -10,6 +10,7 @@ from lms.lms.utils import (
is_instructor, is_instructor,
redirect_to_courses_list, redirect_to_courses_list,
get_average_rating, get_average_rating,
check_multicurrency,
) )
@@ -60,6 +61,11 @@ def set_course_context(context, course_name):
as_dict=True, as_dict=True,
) )
if course.course_price:
course.course_price, course.currency = check_multicurrency(
course.course_price, course.currency
)
if frappe.form_dict.get("edit"): if frappe.form_dict.get("edit"):
if not is_instructor(course.name) and not has_course_moderator_role(): if not is_instructor(course.name) and not has_course_moderator_role():
raise frappe.PermissionError(_("You do not have permission to access this page.")) raise frappe.PermissionError(_("You do not have permission to access this page."))

View File

@@ -7,6 +7,7 @@ from lms.lms.utils import (
has_course_moderator_role, has_course_moderator_role,
get_courses_under_review, get_courses_under_review,
get_average_rating, get_average_rating,
check_multicurrency,
) )
from lms.overrides.user import get_enrolled_courses, get_authored_courses from lms.overrides.user import get_enrolled_courses, get_authored_courses
@@ -58,6 +59,12 @@ def get_courses():
course.enrollment_count = frappe.db.count( course.enrollment_count = frappe.db.count(
"LMS Enrollment", {"course": course.name, "member_type": "Student"} "LMS Enrollment", {"course": course.name, "member_type": "Student"}
) )
if course.course_price:
course.course_price, course.currency = check_multicurrency(
course.course_price, course.currency
)
course.avg_rating = get_average_rating(course.name) or 0 course.avg_rating = get_average_rating(course.name) or 0
if course.upcoming: if course.upcoming:
upcoming_courses.append(course) upcoming_courses.append(course)