feat: apply_gst in batches
This commit is contained in:
@@ -20,10 +20,13 @@
|
|||||||
"allow_student_progress",
|
"allow_student_progress",
|
||||||
"payment_section",
|
"payment_section",
|
||||||
"razorpay_key",
|
"razorpay_key",
|
||||||
"default_currency",
|
|
||||||
"column_break_cfcv",
|
|
||||||
"razorpay_secret",
|
"razorpay_secret",
|
||||||
"apply_gst",
|
"apply_gst",
|
||||||
|
"column_break_cfcv",
|
||||||
|
"default_currency",
|
||||||
|
"show_usd_equivalent",
|
||||||
|
"apply_rounding",
|
||||||
|
"exception_country",
|
||||||
"signup_settings_tab",
|
"signup_settings_tab",
|
||||||
"signup_settings_section",
|
"signup_settings_section",
|
||||||
"terms_of_use",
|
"terms_of_use",
|
||||||
@@ -231,12 +234,31 @@
|
|||||||
"fieldname": "apply_gst",
|
"fieldname": "apply_gst",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply GST for India"
|
"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,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-29 09:54:48.030823",
|
"modified": "2023-09-11 21:56:39.996898",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Settings",
|
"name": "LMS Settings",
|
||||||
|
|||||||
0
lms/lms/doctype/payment_country/__init__.py
Normal file
0
lms/lms/doctype/payment_country/__init__.py
Normal file
8
lms/lms/doctype/payment_country/payment_country.js
Normal file
8
lms/lms/doctype/payment_country/payment_country.js
Normal 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) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
33
lms/lms/doctype/payment_country/payment_country.json
Normal file
33
lms/lms/doctype/payment_country/payment_country.json
Normal 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": []
|
||||||
|
}
|
||||||
9
lms/lms/doctype/payment_country/payment_country.py
Normal file
9
lms/lms/doctype/payment_country/payment_country.py
Normal 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
|
||||||
9
lms/lms/doctype/payment_country/test_payment_country.py
Normal file
9
lms/lms/doctype/payment_country/test_payment_country.py
Normal 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
|
||||||
@@ -3,6 +3,8 @@ import string
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
import razorpay
|
import razorpay
|
||||||
|
import requests
|
||||||
|
import base64
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
||||||
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
@@ -830,19 +832,20 @@ def get_upcoming_evals(student, courses):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_payment_options(doctype, docname, phone):
|
def get_payment_options(doctype, docname, phone, country):
|
||||||
if not frappe.db.exists(doctype, docname):
|
if not frappe.db.exists(doctype, docname):
|
||||||
frappe.throw(_("Invalid document provided."))
|
frappe.throw(_("Invalid document provided."))
|
||||||
|
|
||||||
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)
|
||||||
|
details.amount, details.gst_applied = apply_gst(details, country)
|
||||||
|
|
||||||
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
|
||||||
client = get_client()
|
client = get_client()
|
||||||
order = create_order(client, details.amount, details.currency)
|
order = create_order(client, details.amount, details.currency)
|
||||||
|
|
||||||
options = {
|
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"),
|
"name": frappe.db.get_single_value("Website Settings", "app_name"),
|
||||||
"description": _("Payment for {0} course").format(details["title"]),
|
"description": _("Payment for {0} course").format(details["title"]),
|
||||||
"order_id": order["id"],
|
"order_id": order["id"],
|
||||||
@@ -857,6 +860,42 @@ def get_payment_options(doctype, docname, phone):
|
|||||||
return options
|
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):
|
def get_details(doctype, docname):
|
||||||
if doctype == "LMS Course":
|
if doctype == "LMS Course":
|
||||||
details = frappe.db.get_value(
|
details = frappe.db.get_value(
|
||||||
@@ -896,8 +935,9 @@ def save_address(address):
|
|||||||
|
|
||||||
|
|
||||||
def get_client():
|
def get_client():
|
||||||
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
|
settings = frappe.get_single("LMS Settings")
|
||||||
razorpay_secret = frappe.db.get_single_value("LMS Settings", "razorpay_secret")
|
razorpay_key = settings.razorpay_key
|
||||||
|
razorpay_secret = settings.get_password("razorpay_secret", raise_exception=True)
|
||||||
|
|
||||||
if not razorpay_key and not razorpay_secret:
|
if not razorpay_key and not razorpay_secret:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@@ -946,7 +986,7 @@ def record_payment(address, response, client, doctype, docname):
|
|||||||
address = frappe._dict(json.loads(address))
|
address = frappe._dict(json.loads(address))
|
||||||
address_name = save_address(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 = frappe.new_doc("LMS Payment")
|
||||||
payment_doc.update(
|
payment_doc.update(
|
||||||
{
|
{
|
||||||
@@ -966,10 +1006,13 @@ def record_payment(address, response, client, doctype, docname):
|
|||||||
return payment_doc.name
|
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_field = "course_price" if doctype == "LMS Course" else "amount"
|
||||||
amount = frappe.db.get_value(doctype, docname, amount_field)
|
amount = frappe.db.get_value(doctype, docname, amount_field)
|
||||||
currency = frappe.db.get_value(doctype, docname, "currency")
|
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 {
|
return {
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
@@ -999,3 +1042,11 @@ def add_student_to_batch(batchname, payment):
|
|||||||
)
|
)
|
||||||
student.save(ignore_permissions=True)
|
student.save(ignore_permissions=True)
|
||||||
return f"/batches/{batchname}"
|
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]
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="">
|
<div class="">
|
||||||
<div class="flex mb-2">
|
<div class="flex mb-2">
|
||||||
<div class="field-label">
|
<div class="field-label">
|
||||||
{% set label = "Course Name" if module == "course" else "Batch Name" %}
|
{% set label = "Course" if module == "course" else "Batch" %}
|
||||||
{{ _(label) }} : {{ title }}
|
{{ _(label) }} : {{ title }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,6 +40,11 @@
|
|||||||
{{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }}
|
{{ _("Total Price: ") }} {{ frappe.utils.fmt_money(amount, 2, currency) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if gst_applied %}
|
||||||
|
<span class="small mt-2">
|
||||||
|
{{ _("18% GST included") }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ const generate_payment_link = (e) => {
|
|||||||
doctype: doctype,
|
doctype: doctype,
|
||||||
docname: docname,
|
docname: docname,
|
||||||
phone: address.phone,
|
phone: address.phone,
|
||||||
|
country: address.country,
|
||||||
},
|
},
|
||||||
callback: (data) => {
|
callback: (data) => {
|
||||||
data.message.handler = (response) => {
|
data.message.handler = (response) => {
|
||||||
|
|||||||
@@ -1,23 +1,30 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from lms.lms.utils import check_multicurrency, apply_gst
|
||||||
|
|
||||||
|
|
||||||
def get_context(context):
|
def get_context(context):
|
||||||
module = frappe.form_dict.module
|
module = frappe.form_dict.module
|
||||||
docname = frappe.form_dict.modulename
|
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":
|
if frappe.session.user == "Guest":
|
||||||
raise frappe.PermissionError(_("You are not allowed to access this page."))
|
raise frappe.PermissionError(_("You are not allowed to access this page."))
|
||||||
|
|
||||||
if module not in ["course", "batch"]:
|
if module not in ["course", "batch"]:
|
||||||
raise ValueError(_("Module is incorrect."))
|
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):
|
if not frappe.db.exists(doctype, docname):
|
||||||
raise ValueError(_("Module Name is incorrect or does not exist."))
|
raise ValueError(_("Module Name is incorrect or does not exist."))
|
||||||
|
|
||||||
@@ -35,37 +42,32 @@ def get_context(context):
|
|||||||
if membership:
|
if membership:
|
||||||
raise frappe.PermissionError(_("You are already enrolled for this batch."))
|
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",
|
"LMS Course",
|
||||||
docname,
|
context.docname,
|
||||||
["title", "name", "paid_course", "course_price", "currency"],
|
["title", "name", "paid_course", "course_price as amount", "currency"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not course.paid_course:
|
if not details.paid_course:
|
||||||
raise frappe.PermissionError(_("This course is free."))
|
raise frappe.PermissionError(_("This course is free."))
|
||||||
|
|
||||||
context.title = course.title
|
|
||||||
context.amount = course.course_price
|
|
||||||
context.currency = course.currency
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
batch = frappe.db.get_value(
|
details = frappe.db.get_value(
|
||||||
"LMS Batch",
|
"LMS Batch",
|
||||||
docname,
|
context.docname,
|
||||||
["title", "name", "paid_batch", "amount", "currency"],
|
["title", "name", "paid_batch", "amount", "currency"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not batch.paid_batch:
|
if not details.paid_batch:
|
||||||
raise frappe.PermissionError(
|
raise frappe.PermissionError(
|
||||||
_("To join this batch, please contact the Administrator.")
|
_("To join this batch, please contact the Administrator.")
|
||||||
)
|
)
|
||||||
|
|
||||||
context.title = batch.title
|
context.title = details.title
|
||||||
context.amount = batch.amount
|
context.amount = details.amount
|
||||||
context.currency = batch.currency
|
context.currency = details.currency
|
||||||
|
|
||||||
if context.apply_gst:
|
|
||||||
context.gst_amount = context.amount * 1.18
|
|
||||||
|
|||||||
Reference in New Issue
Block a user