feat: apply_gst in batches

This commit is contained in:
Jannat Patel
2023-09-11 22:44:28 +05:30
parent 07276f5c17
commit 04501143ec
10 changed files with 175 additions and 35 deletions

View File

@@ -20,10 +20,13 @@
"allow_student_progress",
"payment_section",
"razorpay_key",
"default_currency",
"column_break_cfcv",
"razorpay_secret",
"apply_gst",
"column_break_cfcv",
"default_currency",
"show_usd_equivalent",
"apply_rounding",
"exception_country",
"signup_settings_tab",
"signup_settings_section",
"terms_of_use",
@@ -231,12 +234,31 @@
"fieldname": "apply_gst",
"fieldtype": "Check",
"label": "Apply GST for India"
},
{
"default": "0",
"fieldname": "show_usd_equivalent",
"fieldtype": "Check",
"label": "Show USD Equivalent"
},
{
"depends_on": "show_usd_equivalent",
"fieldname": "exception_country",
"fieldtype": "Table MultiSelect",
"label": "Maintain Original Currency",
"options": "Payment Country"
},
{
"default": "0",
"fieldname": "apply_rounding",
"fieldtype": "Check",
"label": "Apply Rounding on Equivalent"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-08-29 09:54:48.030823",
"modified": "2023-09-11 21:56:39.996898",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Settings",

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2023, Frappe and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Payment Country", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,33 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2023-09-11 11:53:16.253740",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"country"
],
"fields": [
{
"fieldname": "country",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Country",
"options": "Country"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-09-11 12:04:56.048632",
"modified_by": "Administrator",
"module": "LMS",
"name": "Payment Country",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class PaymentCountry(Document):
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestPaymentCountry(FrappeTestCase):
pass

View File

@@ -3,6 +3,8 @@ import string
import frappe
import json
import razorpay
import requests
import base64
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
@@ -830,19 +832,20 @@ def get_upcoming_evals(student, courses):
@frappe.whitelist()
def get_payment_options(doctype, docname, phone):
def get_payment_options(doctype, docname, phone, country):
if not frappe.db.exists(doctype, docname):
frappe.throw(_("Invalid document provided."))
validate_phone_number(phone, True)
details = get_details(doctype, docname)
details.amount, details.currency = check_multicurrency(details)
details.amount, details.gst_applied = apply_gst(details, country)
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,
"key_id": frappe.db.get_single_value("LMS Settings", "razorpay_key"),
"name": frappe.db.get_single_value("Website Settings", "app_name"),
"description": _("Payment for {0} course").format(details["title"]),
"order_id": order["id"],
@@ -857,6 +860,42 @@ def get_payment_options(doctype, docname, phone):
return options
def check_multicurrency(amount, currency):
show_usd_equivalent = frappe.db.get_single_value("LMS Settings", "show_usd_equivalent")
exception_country = frappe.db.get_single_value("LMS Settings", "exception_country")
apply_rounding = frappe.db.get_single_value("LMS Settings", "apply_rounding")
country = frappe.db.get_value("User", frappe.session.user, "country")
if not show_usd_equivalent:
return
if currency == "USD":
return
if exception_country and country in exception_country:
return
exchange_rate = get_current_exchange_rate(currency, "USD")
amount = amount * exchange_rate
currency = "USD"
if apply_rounding and amount % 100 != 0:
amount = amount + 100 - amount % 100
return amount, currency
def apply_gst(amount, country):
gst_applied = False
apply_gst = frappe.db.get_single_value("LMS Settings", "apply_gst")
if apply_gst and country == "India":
gst_applied = True
amount = amount * 1.18
return amount, gst_applied
def get_details(doctype, docname):
if doctype == "LMS Course":
details = frappe.db.get_value(
@@ -896,8 +935,9 @@ def save_address(address):
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")
settings = frappe.get_single("LMS Settings")
razorpay_key = settings.razorpay_key
razorpay_secret = settings.get_password("razorpay_secret", raise_exception=True)
if not razorpay_key and not razorpay_secret:
frappe.throw(
@@ -946,7 +986,7 @@ def record_payment(address, response, client, doctype, docname):
address = frappe._dict(json.loads(address))
address_name = save_address(address)
payment_details = get_payment_details(doctype, docname)
payment_details = get_payment_details(doctype, docname, address)
payment_doc = frappe.new_doc("LMS Payment")
payment_doc.update(
{
@@ -966,10 +1006,13 @@ def record_payment(address, response, client, doctype, docname):
return payment_doc.name
def get_payment_details(doctype, docname):
def get_payment_details(doctype, docname, address):
amount_field = "course_price" if doctype == "LMS Course" else "amount"
amount = frappe.db.get_value(doctype, docname, amount_field)
currency = frappe.db.get_value(doctype, docname, "currency")
apply_gst = frappe.db.get_single_value("LMS Settings", "apply_gst")
if apply_gst and address.country == "India":
amount = amount * 1.18
return {
"amount": amount,
@@ -999,3 +1042,11 @@ def add_student_to_batch(batchname, payment):
)
student.save(ignore_permissions=True)
return f"/batches/{batchname}"
def get_current_exchange_rate(source, target="USD"):
url = f"https://api.frankfurter.app/latest?from={source}&to={target}"
response = requests.request("GET", url)
details = response.json()
return details["rates"][target]

View File

@@ -30,7 +30,7 @@
<div class="">
<div class="flex mb-2">
<div class="field-label">
{% set label = "Course Name" if module == "course" else "Batch Name" %}
{% set label = "Course" if module == "course" else "Batch" %}
{{ _(label) }} : {{ title }}
</div>
</div>
@@ -40,6 +40,11 @@
{{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }}
</div>
</div>
{% if gst_applied %}
<span class="small mt-2">
{{ _("18% GST included") }}
</span>
{% endif %}
</div>
</div>
{% endmacro %}

View File

@@ -104,6 +104,7 @@ const generate_payment_link = (e) => {
doctype: doctype,
docname: docname,
phone: address.phone,
country: address.country,
},
callback: (data) => {
data.message.handler = (response) => {

View File

@@ -1,23 +1,30 @@
import frappe
from frappe import _
from lms.lms.utils import check_multicurrency, apply_gst
def get_context(context):
module = frappe.form_dict.module
docname = frappe.form_dict.modulename
doctype = "LMS Course" if module == "course" else "LMS Batch"
context.module = module
context.docname = docname
context.doctype = doctype
validate_access(doctype, docname, module)
get_billing_details(context)
check_multicurrency(context)
apply_gst(context)
def validate_access(doctype, docname, module):
if frappe.session.user == "Guest":
raise frappe.PermissionError(_("You are not allowed to access this page."))
if module not in ["course", "batch"]:
raise ValueError(_("Module is incorrect."))
doctype = "LMS Course" if module == "course" else "LMS Batch"
context.module = module
context.docname = docname
context.doctype = doctype
context.apply_gst = frappe.db.get_single_value("LMS Settings", "apply_gst")
if not frappe.db.exists(doctype, docname):
raise ValueError(_("Module Name is incorrect or does not exist."))
@@ -35,37 +42,32 @@ def get_context(context):
if membership:
raise frappe.PermissionError(_("You are already enrolled for this batch."))
if doctype == "LMS Course":
course = frappe.db.get_value(
def get_billing_details(context):
if context.doctype == "LMS Course":
details = frappe.db.get_value(
"LMS Course",
docname,
["title", "name", "paid_course", "course_price", "currency"],
context.docname,
["title", "name", "paid_course", "course_price as amount", "currency"],
as_dict=True,
)
if not course.paid_course:
if not details.paid_course:
raise frappe.PermissionError(_("This course is free."))
context.title = course.title
context.amount = course.course_price
context.currency = course.currency
else:
batch = frappe.db.get_value(
details = frappe.db.get_value(
"LMS Batch",
docname,
context.docname,
["title", "name", "paid_batch", "amount", "currency"],
as_dict=True,
)
if not batch.paid_batch:
if not details.paid_batch:
raise frappe.PermissionError(
_("To join this batch, please contact the Administrator.")
)
context.title = batch.title
context.amount = batch.amount
context.currency = batch.currency
if context.apply_gst:
context.gst_amount = context.amount * 1.18
context.title = details.title
context.amount = details.amount
context.currency = details.currency