feat: capture gst information

This commit is contained in:
Jannat Patel
2023-08-24 22:15:55 +05:30
parent 47c19b4e3d
commit d5f10db250
18 changed files with 371 additions and 271 deletions

View File

@@ -9,23 +9,10 @@
"field_order": [
"student_details_section",
"student",
"payment",
"column_break_oduu",
"student_name",
"username",
"payment_details_section",
"column_break_zvlp",
"amount",
"currency",
"column_break_clem",
"order_id",
"payment_id",
"payment_received",
"address_details_section",
"address",
"pan",
"column_break_rqoj",
"gstin",
"gst_category"
"username"
],
"fields": [
{
@@ -51,43 +38,6 @@
"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"
},
{
"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"
},
{
"fieldname": "student_details_section",
"fieldtype": "Section Break",
@@ -98,43 +48,16 @@
"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"
"fieldname": "payment",
"fieldtype": "Link",
"label": "Payment",
"options": "LMS Payment"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-08-22 21:59:16.678547",
"modified": "2023-08-24 17:48:53.045539",
"modified_by": "Administrator",
"module": "LMS",
"name": "Class Student",

View File

@@ -7,22 +7,15 @@
"field_order": [
"course",
"member_type",
"batch",
"payment",
"column_break_3",
"member",
"member_name",
"member_username",
"billing_information_section",
"address",
"amount",
"currency",
"column_break_rvzn",
"order_id",
"payment_id",
"payment_received",
"section_break_8",
"cohort",
"subgroup",
"batch",
"column_break_12",
"current_lesson",
"progress",
@@ -122,52 +115,15 @@
"fieldtype": "Section Break"
},
{
"fieldname": "billing_information_section",
"fieldtype": "Section Break",
"label": "Billing Information"
},
{
"fieldname": "address",
"fieldname": "payment",
"fieldtype": "Link",
"label": "Address",
"options": "Address"
},
{
"default": "0",
"fieldname": "payment_received",
"fieldtype": "Check",
"label": "Payment Received"
},
{
"fieldname": "column_break_rvzn",
"fieldtype": "Column Break"
},
{
"fieldname": "order_id",
"fieldtype": "Data",
"label": "Order ID"
},
{
"fieldname": "payment_id",
"fieldtype": "Data",
"label": "Payment ID"
},
{
"fieldname": "amount",
"fieldtype": "Data",
"label": "Amount",
"options": "currency"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
"label": "Payment",
"options": "LMS Payment"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-08-17 13:52:49.450499",
"modified": "2023-08-24 17:52:35.487141",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Batch Membership",

View File

@@ -21,7 +21,7 @@
"seat_count",
"section_break_6",
"description",
"prerequisite",
"class_details",
"students",
"courses",
"section_break_gsac",
@@ -188,15 +188,15 @@
"options": "Currency"
},
{
"fieldname": "prerequisite",
"fieldtype": "Small Text",
"label": "Prerequisite",
"fieldname": "class_details",
"fieldtype": "Text Editor",
"label": "Class Details",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-08-22 11:53:22.248596",
"modified": "2023-08-23 17:35:42.750754",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Class",

View File

@@ -191,7 +191,7 @@ def create_class(
start_date,
end_date,
description=None,
prerequisite=None,
class_details=None,
seat_count=0,
start_time=None,
end_time=None,
@@ -204,17 +204,17 @@ def create_class(
):
frappe.only_for("Moderator")
if name:
class_details = frappe.get_doc("LMS Class", name)
doc = frappe.get_doc("LMS Class", name)
else:
class_details = frappe.get_doc({"doctype": "LMS Class"})
doc = frappe.get_doc({"doctype": "LMS Class"})
class_details.update(
doc.update(
{
"title": title,
"start_date": start_date,
"end_date": end_date,
"description": description,
"prerequisite": prerequisite,
"class_details": class_details,
"seat_count": seat_count,
"start_time": start_time,
"end_time": end_time,
@@ -225,8 +225,8 @@ def create_class(
"currency": currency,
}
)
class_details.save()
return class_details
doc.save()
return doc
@frappe.whitelist()

View File

View File

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

View File

@@ -0,0 +1,139 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:PAY-{#####}",
"creation": "2023-08-24 17:46:52.065763",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"member",
"column_break_rqkd",
"billing_name",
"payment_received",
"payment_details_section",
"amount",
"currency",
"column_break_yxpl",
"order_id",
"payment_id",
"billing_details_section",
"address",
"column_break_monu",
"gstin",
"pan"
],
"fields": [
{
"fieldname": "order_id",
"fieldtype": "Data",
"label": "Order ID"
},
{
"fieldname": "payment_id",
"fieldtype": "Data",
"label": "Payment ID"
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "column_break_rqkd",
"fieldtype": "Column Break"
},
{
"fieldname": "gstin",
"fieldtype": "Data",
"label": "GSTIN"
},
{
"fieldname": "pan",
"fieldtype": "Data",
"label": "PAN"
},
{
"fieldname": "address",
"fieldtype": "Link",
"label": "Address",
"options": "Address"
},
{
"default": "0",
"fieldname": "payment_received",
"fieldtype": "Check",
"label": "Payment Received"
},
{
"fetch_from": "user.full_name",
"fieldname": "billing_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Billing Name",
"reqd": 1
},
{
"fieldname": "payment_details_section",
"fieldtype": "Section Break",
"label": "Payment Details"
},
{
"fieldname": "column_break_yxpl",
"fieldtype": "Column Break"
},
{
"fieldname": "billing_details_section",
"fieldtype": "Section Break",
"label": "Billing Details"
},
{
"fieldname": "column_break_monu",
"fieldtype": "Column Break"
},
{
"fieldname": "member",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Member",
"options": "User",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-08-24 22:08:12.294960",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Payment",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"show_title_field_in_link": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "billing_name"
}

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 LMSPayment(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 TestLMSPayment(FrappeTestCase):
pass

View File

@@ -835,24 +835,7 @@ def get_payment_options(doctype, docname, phone):
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."))
details = get_details(doctype, docname)
razorpay_key = frappe.db.get_single_value("LMS Settings", "razorpay_key")
client = get_client()
@@ -874,8 +857,30 @@ def get_payment_options(doctype, docname, phone):
return options
def get_details(doctype, docname):
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."))
return details
def save_address(address):
address = json.loads(address)
address.update(
{
"address_title": frappe.db.get_value("User", frappe.session.user, "full_name"),
@@ -930,62 +935,76 @@ def verify_payment(response, doctype, docname, address, order_id):
}
)
payment = record_payment(address, response, client, doctype, docname)
if doctype == "LMS Course":
return create_membership(address, response, docname, client)
return create_membership(docname, payment)
else:
return add_student_to_class(address, response, docname, client)
return add_student_to_class(docname, payment)
def create_membership(address, response, course, client):
def record_payment(address, response, client, doctype, docname):
address = frappe._dict(json.loads(address))
address_name = save_address(address)
payment_details = get_payment_details(client, response, doctype, docname)
payment_doc = frappe.new_doc("LMS Payment")
payment_doc.update(
{
"member": frappe.session.user,
"billing_name": address.billing_name,
"address": address_name,
"payment_received": 1,
"order_id": response["razorpay_order_id"],
"payment_id": response["razorpay_payment_id"],
"amount": payment_details["amount"],
"currency": payment_details["currency"],
"gstin": address.gstin,
"pan": address.pan,
}
)
payment_doc.save(ignore_permissions=True)
return payment_doc.name
def get_payment_details(client, response, doctype, docname):
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)
)
frappe.log_error(e, "Error during payment fetch")
if payment:
amount = payment["amount"] / 100
currency = payment["currency"]
else:
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")
return {
"amount": amount,
"currency": currency,
}
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")
def create_membership(course, payment):
membership = frappe.new_doc("LMS Batch Membership")
membership.update(
{"member": frappe.session.user, "course": course, "payment": payment}
)
membership.save(ignore_permissions=True)
return f"/courses/{course}/learn/1.1"
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)
)
def add_student_to_class(classname, payment):
student = frappe.new_doc("Class Student")
student.update(
{
"student": frappe.session.user,
"payment": payment,
"parent": classname,
"parenttype": "LMS Class",
"parentfield": "students",
}
)
student.save(ignore_permissions=True)
return f"/classes/{classname}"

View File

@@ -2188,10 +2188,6 @@ select {
border-bottom: none;
}
.awesomplete ul li:last-child {
display: none;
}
.students-parent {
display: grid;
grid-template-columns: repeat(auto-fill, 150px);

View File

@@ -321,10 +321,10 @@ const open_class_dialog = () => {
reqd: 1,
},
{
fieldtype: "Small Text",
label: __("Prerequisite"),
fieldname: "prerequisite",
default: class_info && class_info.prerequisite,
fieldtype: "Text Editor",
label: __("Class Details"),
fieldname: "class_details",
default: class_info && class_info.class_details,
reqd: 1,
},
{

View File

@@ -13,6 +13,12 @@ frappe.ready(() => {
const setup_billing = () => {
this.billing = new frappe.ui.FieldGroup({
fields: [
{
fieldtype: "Data",
label: __("Billing Name"),
fieldname: "billing_name",
reqd: 1,
},
{
fieldtype: "Data",
label: __("Address Line 1"),
@@ -30,14 +36,14 @@ const setup_billing = () => {
fieldname: "city",
reqd: 1,
},
{
fieldtype: "Column Break",
},
{
fieldtype: "Data",
label: __("State/Province"),
fieldname: "state",
},
{
fieldtype: "Column Break",
},
{
fieldtype: "Link",
label: __("Country"),
@@ -58,17 +64,25 @@ const setup_billing = () => {
fieldname: "phone",
reqd: 1,
},
{
fieldtype: "Section Break",
label: __("GST Details"),
fieldname: "gst_details",
depends_on: "eval:doc.country === 'India'",
},
{
fieldtype: "Data",
fieldname: "gstin",
label: __("GSTIN"),
depends_on: (doc) => console.log(doc.country),
fieldname: "gstin",
},
{
fieldtype: "Column Break",
fieldname: "gst_details_break",
},
{
fieldtype: "Data",
fieldname: "pan",
label: __("PAN"),
depends_on: (doc) => console.log(doc.country),
},
],
body: $("#billing-form").get(0),

View File

@@ -39,7 +39,7 @@ def get_context(context):
"paid_class",
"amount",
"currency",
"prerequisite",
"class_details",
],
as_dict=True,
)

View File

@@ -10,7 +10,7 @@
<div class="container">
{{ CourseHeaderOverlay(class_info, courses, students) }}
<div class="pt-10">
{{ Prerequisites(class_info) }}
{{ ClassDetails(class_info) }}
{{ CourseList(courses) }}
</div>
</div>
@@ -42,15 +42,6 @@
{% macro ClassHeaderDetails(class_info, courses, students) %}
<div class="class-details" data-class="{{ class_info.name }}">
<div class="flex align-center">
<span>
{{ courses | length }} {{ _("Courses") }}
</span>
<span class="px-2"> · </span>
<span>
{{ students | length }} {{ _("Students") }}
</span>
</div>
<div class="page-title">
{{ class_info.title }}
@@ -92,6 +83,19 @@
<div class="course-overlay-card class-overlay">
<div class="course-overlay-content">
{% if class_info.seat_count %}
{% if seats_left %}
<div class="indicator-pill green pull-right">
{{ _("Seats Available") }}: {{ seats_left }}
</div>
{% else %}
<div class="indicator-pill red pull-right">
{{ _("No seats left") }}
</div>
{% endif %}
{% endif %}
{% if class_info.paid_class %}
<div class="bold-heading">
{{ frappe.utils.fmt_money(class_info.amount, 0, class_info.currency) }}
@@ -105,13 +109,6 @@
{{ courses | length }} {{ _("Courses") }}
</div>
<div class="vertically-center mt-2">
<svg class="icon icon-md mr-1">
<use class="" href="#icon-users">
</svg>
{{ students | length }} {{ _("Students") }}
</div>
<div class="mt-2">
<svg class="icon icon-sm">
<use href="#icon-calendar"></use>
@@ -144,7 +141,8 @@
{{ _("Checkout Class") }}
</a>
{% elif class_info.paid_class %}
<a class="btn btn-primary wide-button" href="/billing/class/{{ class_info.name }}">
<a class="btn btn-primary wide-button {% if class_info.seat_count and not seats_left %} hide {% endif %}"
href="/billing/class/{{ class_info.name }}">
{{ _("Register Now") }}
</a>
{% else %}
@@ -165,13 +163,10 @@
{% endmacro %}
{% macro Prerequisites(class_info) %}
{% macro ClassDetails(class_info) %}
<div class="course-description-section w-50">
<div class="page-title">
{{ _("Prerequisite Knowledge") }}
</div>
<div class="mt-2">
{{ class_info.prerequisite }}
{{ class_info.class_details }}
</div>
</div>
{% endmacro %}

View File

@@ -14,7 +14,7 @@ def get_context(context):
"name",
"title",
"description",
"prerequisite",
"class_details",
"start_date",
"end_date",
"paid_class",
@@ -22,6 +22,7 @@ def get_context(context):
"currency",
"start_time",
"end_time",
"seat_count",
],
as_dict=1,
)
@@ -40,12 +41,8 @@ def get_context(context):
)
)
context.students = frappe.get_all(
"Class Student",
{"parent": class_name},
["name", "student", "student_name", "username"],
order_by="creation desc",
)
context.student_count = frappe.db.count("Class Student", {"parent": class_name})
context.seats_left = context.class_info.seat_count - context.student_count
context.is_moderator = has_course_moderator_role()
context.is_evaluator = has_course_evaluator_role()

View File

@@ -90,11 +90,21 @@
{% macro ClassCards(classes, show_price=False) %}
<div class="lms-card-parent">
{% for class in classes %}
{% set course_count = frappe.db.count("Class Course", {"parent": class.name}) %}
{% set student_count = frappe.db.count("Class Student", {"parent": class.name}) %}
<div class="common-card-style column-card" style="min-height: 150px;">
{% if class.seat_count %}
{% if class.seats_left > 0 %}
<div class="indicator-pill green align-self-start mb-2">
{{ _("Seats Available") }}: {{ class.seats_left }}
</div>
{% else %}
<div class="indicator-pill red align-self-start mb-2">
{{ _("No Seats Left") }}
</div>
{% endif %}
{% endif %}
<div class="bold-heading">
{{ class.title }}
</div>
@@ -127,15 +137,15 @@
<svg class="icon icon-md">
<use href="#icon-education"></use>
</svg>
{{ course_count }} {{ _("Courses") }}
{{ class.course_count }} {{ _("Courses") }}
</div>
<div class="mb-2">
<!-- <div class="mb-2">
<svg class="icon icon-md">
<use href="#icon-users"></use>
</svg>
{{ student_count }} {{ _("Students") }}
</div>
</div> -->
{% if is_student(class.name) %}
<a class="stretched-link" href="/classes/{{ class.name }}"></a>

View File

@@ -20,10 +20,17 @@ def get_context(context):
"currency",
"seat_count",
],
order_by="start_date",
)
past_classes, upcoming_classes = [], []
for class_ in classes:
class_.student_count = frappe.db.count("Class Student", {"parent": class_.name})
class_.course_count = frappe.db.count("Class Course", {"parent": class_.name})
class_.seats_left = (
class_.seat_count - class_.student_count if class_.seat_count else None
)
print(class_.seat_count, class_.student_count, class_.seats_left)
if getdate(class_.start_date) < getdate():
past_classes.append(class_)
else:
@@ -39,13 +46,31 @@ def get_context(context):
)
for class_ in my_classes:
my_classes_info.append(
frappe.db.get_value(
"LMS Class",
class_,
["name", "title", "start_date", "end_date", "paid_class", "seat_count"],
as_dict=True,
)
class_info = frappe.db.get_value(
"LMS Class",
class_,
[
"name",
"title",
"description",
"start_date",
"end_date",
"paid_class",
"amount",
"currency",
"seat_count",
],
as_dict=True,
)
class_info.student_count = frappe.db.count(
"Class Student", {"parent": class_info.name}
)
class_info.course_count = frappe.db.count(
"Class Course", {"parent": class_info.name}
)
class_info.seats_left = class_info.seat_count - class_info.student_count
my_classes_info.append(class_info)
context.my_classes = my_classes_info