Merge pull request #2167 from raizasafeel/fix/payment

fix: use backend field metadata for billing and transaction forms
This commit is contained in:
Jannat Patel
2026-03-06 17:42:39 +05:30
committed by GitHub
5 changed files with 170 additions and 61 deletions
@@ -55,17 +55,18 @@
:label="__('Member')"
doctype="User"
v-model="transactionData.member"
:required="true"
:required="!!fieldMeta.member?.reqd"
/>
<FormControl
:label="__('Billing Name')"
v-model="transactionData.billing_name"
:required="true"
:required="!!fieldMeta.billing_name?.reqd"
/>
<Link
:label="__('Source')"
v-model="transactionData.source"
doctype="LMS Source"
:required="!!fieldMeta.source?.reqd"
/>
<FormControl
type="select"
@@ -73,12 +74,14 @@
:label="__('Payment For Document Type')"
v-model="transactionData.payment_for_document_type"
doctype="DocType"
:required="!!fieldMeta.payment_for_document_type?.reqd"
/>
<Link
v-if="transactionData.payment_for_document_type"
:label="__('Payment For Document')"
v-model="transactionData.payment_for_document"
:doctype="transactionData.payment_for_document_type"
:required="!!fieldMeta.payment_for_document?.reqd"
/>
</div>
@@ -90,17 +93,18 @@
:label="__('Currency')"
v-model="transactionData.currency"
doctype="Currency"
:required="true"
:required="!!fieldMeta.currency?.reqd"
/>
<FormControl
:label="__('Amount')"
v-model="transactionData.amount"
:required="true"
:required="!!fieldMeta.amount?.reqd"
/>
<FormControl
v-if="transactionData.amount_with_gst"
:label="__('Amount with GST')"
v-model="transactionData.amount_with_gst"
:required="!!fieldMeta.amount_with_gst?.reqd"
/>
</div>
@@ -113,21 +117,25 @@
v-if="transactionData.coupon"
:label="__('Coupon Code')"
v-model="transactionData.coupon"
:required="!!fieldMeta.coupon?.reqd"
/>
<FormControl
v-if="transactionData.coupon"
:label="__('Coupon Code')"
v-model="transactionData.coupon_code"
:required="!!fieldMeta.coupon_code?.reqd"
/>
<FormControl
v-if="transactionData.coupon"
:label="__('Discount Amount')"
v-model="transactionData.discount_amount"
:required="!!fieldMeta.discount_amount?.reqd"
/>
<FormControl
v-if="transactionData.coupon"
:label="__('Original Amount')"
v-model="transactionData.original_amount"
:required="!!fieldMeta.original_amount?.reqd"
/>
</div>
</div>
@@ -140,17 +148,27 @@
:label="__('Address')"
v-model="transactionData.address"
doctype="Address"
:required="true"
:required="!!fieldMeta.address?.reqd"
/>
<FormControl
:label="__('GSTIN')"
v-model="transactionData.gstin"
:required="!!fieldMeta.gstin?.reqd"
/>
<FormControl
:label="__('PAN')"
v-model="transactionData.pan"
:required="!!fieldMeta.pan?.reqd"
/>
<FormControl :label="__('GSTIN')" v-model="transactionData.gstin" />
<FormControl :label="__('PAN')" v-model="transactionData.pan" />
<FormControl
:label="__('Payment ID')"
v-model="transactionData.payment_id"
:required="!!fieldMeta.payment_id?.reqd"
/>
<FormControl
:label="__('Order ID')"
v-model="transactionData.order_id"
:required="!!fieldMeta.order_id?.reqd"
/>
</div>
</div>
@@ -171,6 +189,10 @@ const show = defineModel('show')
const props = defineProps<{
transactions: any
data: any
fieldMeta: Record<
string,
{ reqd?: number; default?: string; description?: string }
>
}>()
const saveTransaction = () => {
@@ -211,48 +233,49 @@ const updateTransaction = () => {
}
const openDetails = () => {
if (props.data) {
const docType = props.data.payment_for_document_type
const docName = props.data.payment_for_document
if (docType && docName) {
router.push({
name: docType == 'LMS Course' ? 'CourseDetail' : 'BatchDetail',
params: {
[docType == 'LMS Course' ? 'courseName' : 'batchName']: docName,
},
})
}
const docType = transactionData.value?.payment_for_document_type
const docName = transactionData.value?.payment_for_document
if (docType && docName) {
router.push({
name: docType == 'LMS Course' ? 'CourseDetail' : 'BatchDetail',
params: {
[docType == 'LMS Course' ? 'courseName' : 'batchName']: docName,
},
})
show.value = false
}
}
const emptyTransactionData = {
const getDefault = (fieldname: string) =>
props.fieldMeta[fieldname]?.default || null
const getEmptyTransactionData = () => ({
payment_received: false,
payment_for_certificate: false,
member: null,
billing_name: null,
source: null,
payment_for_document_type: null,
payment_for_document: null,
member: getDefault('member'),
billing_name: getDefault('billing_name'),
source: getDefault('source'),
payment_for_document_type: getDefault('payment_for_document_type'),
payment_for_document: getDefault('payment_for_document'),
member_consent: false,
currency: null,
amount: null,
amount_with_gst: null,
coupon: null,
coupon_code: null,
discount_amount: null,
original_amount: null,
order_id: null,
payment_id: null,
gstin: null,
pan: null,
address: null,
}
currency: getDefault('currency'),
amount: getDefault('amount'),
amount_with_gst: getDefault('amount_with_gst'),
coupon: getDefault('coupon'),
coupon_code: getDefault('coupon_code'),
discount_amount: getDefault('discount_amount'),
original_amount: getDefault('original_amount'),
order_id: getDefault('order_id'),
payment_id: getDefault('payment_id'),
gstin: getDefault('gstin'),
pan: getDefault('pan'),
address: getDefault('address'),
})
watch(
() => props.data,
(newVal) => {
transactionData.value = newVal ? { ...newVal } : emptyTransactionData
transactionData.value = newVal ? { ...newVal } : getEmptyTransactionData()
},
{ immediate: true }
)
@@ -3,6 +3,7 @@
v-if="step == 'new'"
:transactions="transactions"
:data="data"
:fieldMeta="fieldMeta.data || {}"
v-model:show="show"
@updateStep="updateStep"
/>
@@ -17,13 +18,14 @@
v-else-if="step == 'details'"
:transactions="transactions"
:data="data"
:fieldMeta="fieldMeta.data || {}"
v-model:show="show"
@updateStep="updateStep"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { createListResource } from 'frappe-ui'
import { createListResource, createResource } from 'frappe-ui'
import TransactionList from '@/components/Settings/Transactions/TransactionList.vue'
import TransactionDetails from '@/components/Settings/Transactions/TransactionDetails.vue'
@@ -45,6 +47,11 @@ const updateStep = (newStep: 'list' | 'new' | 'edit', newData: any) => {
}
}
const fieldMeta = createResource({
url: 'lms.lms.api.get_payment_field_meta',
auto: true,
})
const transactions = createListResource({
doctype: 'LMS Payment',
fields: [
+35 -21
View File
@@ -114,25 +114,27 @@
<FormControl
:label="__('Billing Name')"
v-model="billingDetails.billing_name"
:required="true"
:required="!!fieldMeta.billing_name?.reqd"
/>
<FormControl
:label="__('Address Line 1')"
v-model="billingDetails.address_line1"
:required="true"
:required="!!fieldMeta.address_line1?.reqd"
/>
<FormControl
:label="__('Address Line 2')"
v-model="billingDetails.address_line2"
:required="!!fieldMeta.address_line2?.reqd"
/>
<FormControl
:label="__('City')"
v-model="billingDetails.city"
:required="true"
:required="!!fieldMeta.city?.reqd"
/>
<FormControl
:label="__('State/Province')"
v-model="billingDetails.state"
:required="!!fieldMeta.state?.reqd"
/>
</div>
<div class="space-y-4">
@@ -141,34 +143,36 @@
:value="billingDetails.country"
@change="(option) => changeCurrency(option)"
:label="__('Country')"
:required="true"
:required="!!fieldMeta.country?.reqd"
/>
<FormControl
:label="__('Postal Code')"
v-model="billingDetails.pincode"
:required="true"
:required="!!fieldMeta.pincode?.reqd"
/>
<FormControl
:label="__('Phone Number')"
v-model="billingDetails.phone"
:required="true"
:required="!!fieldMeta.phone?.reqd"
/>
<Link
doctype="LMS Source"
:value="billingDetails.source"
@change="(option) => (billingDetails.source = option)"
:label="__('Where did you hear about us?')"
:required="true"
:required="!!fieldMeta.source?.reqd"
/>
<FormControl
v-if="billingDetails.country == 'India'"
:label="__('GST Number')"
v-model="billingDetails.gstin"
:required="!!fieldMeta.gstin?.reqd"
/>
<FormControl
v-if="billingDetails.country == 'India'"
:label="__('PAN Number')"
v-model="billingDetails.pan"
:required="!!fieldMeta.pan?.reqd"
/>
</div>
</div>
@@ -273,6 +277,7 @@ const access = createResource({
name: props.name,
},
onSuccess(data) {
Object.assign(fieldMeta, data.billing_field_meta || {})
setBillingDetails(data.address)
orderSummary.submit()
},
@@ -295,19 +300,24 @@ const orderSummary = createResource({
const appliedCoupon = ref(null)
const billingDetails = reactive({})
const fieldMeta = reactive({})
const getDefault = (fieldname) => fieldMeta[fieldname]?.default || ''
const setBillingDetails = (data) => {
billingDetails.billing_name = data?.billing_name || ''
billingDetails.address_line1 = data?.address_line1 || ''
billingDetails.address_line2 = data?.address_line2 || ''
billingDetails.city = data?.city || ''
billingDetails.state = data?.state || ''
billingDetails.country = data?.country || ''
billingDetails.pincode = data?.pincode || ''
billingDetails.phone = data?.phone || ''
billingDetails.source = data?.source || ''
billingDetails.gstin = data?.gstin || ''
billingDetails.pan = data?.pan || ''
billingDetails.billing_name = data?.billing_name || getDefault('billing_name')
billingDetails.address_line1 =
data?.address_line1 || getDefault('address_line1')
billingDetails.address_line2 =
data?.address_line2 || getDefault('address_line2')
billingDetails.city = data?.city || getDefault('city')
billingDetails.state = data?.state || getDefault('state')
billingDetails.country = data?.country || getDefault('country')
billingDetails.pincode = data?.pincode || getDefault('pincode')
billingDetails.phone = data?.phone || getDefault('phone')
billingDetails.source = data?.source || getDefault('source')
billingDetails.gstin = data?.gstin || getDefault('gstin')
billingDetails.pan = data?.pan || getDefault('pan')
}
const paymentLink = createResource({
@@ -336,7 +346,7 @@ const generatePaymentLink = () => {
{},
{
validate() {
if (!billingDetails.source) {
if (!billingDetails.source && fieldMeta.source?.reqd) {
return __('Please let us know where you heard about us from.')
}
if (!billingDetails.member_consent) {
@@ -370,15 +380,19 @@ function removeCoupon() {
}
const validateAddress = () => {
let mandatoryFields = [
let billingFields = [
'billing_name',
'address_line1',
'address_line2',
'city',
'state',
'pincode',
'country',
'phone',
'source',
'gstin',
'pan',
]
let mandatoryFields = billingFields.filter((f) => fieldMeta[f]?.reqd)
for (let field of mandatoryFields) {
if (!billingDetails[field])
return (
+49 -1
View File
@@ -37,6 +37,7 @@ from lms.lms.utils import (
get_average_rating,
get_batch_details,
get_course_details,
get_field_meta,
get_instructors,
get_lesson_count,
get_lms_route,
@@ -103,7 +104,54 @@ def validate_billing_access(billing_type: str, name: str):
as_dict=1,
)
return {"access": access, "message": message, "address": address}
payment_fields = get_payment_field_meta()
address_fields = get_field_meta(
"Address",
[
"address_line1",
"address_line2",
"city",
"state",
"country",
"pincode",
"phone",
],
)
billing_field_meta = {**payment_fields, **address_fields}
return {
"access": access,
"message": message,
"address": address,
"billing_field_meta": billing_field_meta,
}
@frappe.whitelist()
def get_payment_field_meta():
return get_field_meta(
"LMS Payment",
[
"member",
"billing_name",
"source",
"payment_for_document_type",
"payment_for_document",
"currency",
"amount",
"amount_with_gst",
"original_amount",
"discount_amount",
"coupon",
"coupon_code",
"address",
"gstin",
"pan",
"payment_id",
"order_id",
"member_consent",
],
)
def verify_billing_access(doctype, name, billing_type):
+17
View File
@@ -2328,3 +2328,20 @@ def recalculate_course_progress(course: str, member: str):
)
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
update_program_progress(member)
def get_field_meta(doctype, fieldnames):
"""Returns field metadata for 'fieldnames' from 'doctype'"""
meta = frappe.get_meta(doctype)
fieldnames_meta = {}
for fieldname in fieldnames:
field = meta.get_field(fieldname)
if field:
fieldnames_meta[fieldname] = {
"reqd": field.reqd,
"default": field.default,
"description": field.description,
}
return fieldnames_meta