feat: implement coupon code manage

ment with billing and transaction integration; bug fixes
This commit is contained in:
Joedeep Singh
2025-10-13 13:03:43 +00:00
parent bf36890bd3
commit 6933105261
10 changed files with 221 additions and 154 deletions
-2
View File
@@ -10,7 +10,6 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
Annoucements: typeof import('./src/components/Annoucements.vue')['default'] Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default'] AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
Apps: typeof import('./src/components/Apps.vue')['default'] Apps: typeof import('./src/components/Apps.vue')['default']
AppSidebar: typeof import('./src/components/AppSidebar.vue')['default'] AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default'] AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
@@ -74,7 +73,6 @@ declare module 'vue' {
InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default'] InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default']
JobApplicationModal: typeof import('./src/components/Modals/JobApplicationModal.vue')['default'] JobApplicationModal: typeof import('./src/components/Modals/JobApplicationModal.vue')['default']
JobCard: typeof import('./src/components/JobCard.vue')['default'] JobCard: typeof import('./src/components/JobCard.vue')['default']
LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default']
LessonContent: typeof import('./src/components/LessonContent.vue')['default'] LessonContent: typeof import('./src/components/LessonContent.vue')['default']
LessonHelp: typeof import('./src/components/LessonHelp.vue')['default'] LessonHelp: typeof import('./src/components/LessonHelp.vue')['default']
Link: typeof import('./src/components/Controls/Link.vue')['default'] Link: typeof import('./src/components/Controls/Link.vue')['default']
@@ -24,15 +24,28 @@
:label="__('Discount Amount')" :label="__('Discount Amount')"
type="number" type="number"
/> />
<FormControl v-model="doc.expires_on" :label="__('Expires On')" type="date" /> <FormControl
<FormControl v-model="doc.usage_limit" :label="__('Usage Limit')" type="number" /> v-model="doc.expires_on"
:label="__('Expires On')"
type="date"
:description="__('Leave blank for no expiry')"
/>
<FormControl
v-model="doc.usage_limit"
:label="__('Usage Limit')"
type="number"
:placeholder="__('Unlimited')"
/>
<Switch v-model="doc.active" :label="__('Active')" /> <Switch v-model="doc.active" :label="__('Active')" />
<div class="col-span-2"> <div class="col-span-2">
<div class="text-sm font-medium text-ink-gray-7 mb-2">{{ __('Applicable Items (optional)') }}</div> <div class="text-md font-medium text-ink-gray-7 mb-1 mt-2">{{ __('Select Courses/Batches') }}<span class="text-ink-red-3">*</span></div>
<div class="space-y-2"> <div class="space-y-2">
<div v-for="(row, idx) in doc.applicable_items" :key="idx" class="flex gap-2 items-center"> <div v-for="(row, idx) in doc.applicable_items" :key="idx" class="flex gap-2 items-end">
<FormControl v-model="row.reference_doctype" :label="__('Type')" type="select" :options="['LMS Course', 'LMS Batch']" /> <FormControl class="w-28" v-model="row.reference_doctype" :label="__('Type')" type="select" :options="[
<Link :doctype="row.reference_doctype || 'LMS Course'" :label="__('Item')" :value="row.reference_name" @change="(opt) => (row.reference_name = opt)" /> { label: 'Course ', value: 'LMS Course' },
{ label: 'Batch ', value: 'LMS Batch' }
]" />
<Link class="min-w-40" :doctype="row.reference_doctype || 'LMS Course'" :label="__('Name')" :value="row.reference_name" @change="(opt) => (row.reference_name = opt)" />
<Button variant="subtle" @click="removeRow(idx)"> <Button variant="subtle" @click="removeRow(idx)">
<X class="h-3 w-3" /> <X class="h-3 w-3" />
</Button> </Button>
@@ -135,7 +148,6 @@ function save() {
emit('saved') emit('saved')
}, },
onError(err) { onError(err) {
console.log('Save error:', err)
toast.error(err.messages?.[0] || err.message || err) toast.error(err.messages?.[0] || err.message || err)
} }
}) })
@@ -147,7 +159,6 @@ function save() {
emit('saved') emit('saved')
}, },
onError(err) { onError(err) {
console.log('Insert error:', err)
toast.error(err.messages?.[0] || err.message || err) toast.error(err.messages?.[0] || err.message || err)
} }
}) })
+41 -3
View File
@@ -27,6 +27,7 @@
<th class="text-left p-2">{{ __('Expires On') }}</th> <th class="text-left p-2">{{ __('Expires On') }}</th>
<th class="text-left p-2">{{ __('Usage') }}</th> <th class="text-left p-2">{{ __('Usage') }}</th>
<th class="text-left p-2">{{ __('Active') }}</th> <th class="text-left p-2">{{ __('Active') }}</th>
<th class="text-right p-2 w-8"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -43,6 +44,13 @@
<Badge v-if="row.active" theme="green">{{ __('Enabled') }}</Badge> <Badge v-if="row.active" theme="green">{{ __('Enabled') }}</Badge>
<Badge v-else theme="gray">{{ __('Disabled') }}</Badge> <Badge v-else theme="gray">{{ __('Disabled') }}</Badge>
</td> </td>
<td class="p-2 text-right" @click.stop>
<Button variant="ghost" @click="confirmDelete(row)">
<template #icon>
<Trash2 class="h-4 w-4 stroke-1.5 text-ink-red-4" />
</template>
</Button>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -52,11 +60,14 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { Button, Badge, createListResource } from 'frappe-ui' import { Button, Badge, createListResource, toast, call } from 'frappe-ui'
import { ref } from 'vue' import { ref, getCurrentInstance } from 'vue'
import { Plus } from 'lucide-vue-next' import { Plus, Trash2 } from 'lucide-vue-next'
import CouponDetails from '@/components/Settings/CouponDetails.vue' import CouponDetails from '@/components/Settings/CouponDetails.vue'
const app = getCurrentInstance()
const { $dialog } = app.appContext.config.globalProperties
defineProps({ defineProps({
label: String, label: String,
description: String, description: String,
@@ -78,6 +89,7 @@ const coupons = createListResource({
'times_redeemed', 'times_redeemed',
'active', 'active',
], ],
auto: true,
}) })
function openForm(id) { function openForm(id) {
@@ -88,5 +100,31 @@ function openForm(id) {
function onSaved() { function onSaved() {
coupons.reload() coupons.reload()
} }
function confirmDelete(row) {
$dialog({
title: __('Delete this coupon?'),
message: __('This will permanently delete the coupon and the code will no longer work. Are you sure?'),
actions: [
{
label: __('Delete'),
theme: 'red',
variant: 'solid',
onClick({ close }) {
trashCoupon(row.name, close)
close()
},
},
],
})
}
function trashCoupon(name, close) {
call('frappe.client.delete', { doctype: 'LMS Coupon', name }).then(() => {
toast.success(__('Coupon deleted successfully'))
coupons.reload()
if (typeof close === 'function') close()
})
}
</script> </script>
@@ -67,43 +67,32 @@
/> />
<FormControl :label="__('Amount')" v-model="transactionData.amount" /> <FormControl :label="__('Amount')" v-model="transactionData.amount" />
<FormControl <FormControl
:label="__('Order ID')" v-if="transactionData.coupon"
v-model="transactionData.order_id" :label="__('Coupon Code')"
v-model="transactionData.coupon"
:disabled="true"
/> />
</div> </div>
<div <div class="grid grid-cols-3 gap-5 mt-5">
v-if="transactionData && (transactionData.coupon || transactionData.discount_amount || transactionData.discount_percent)" <FormControl
class="mt-10" v-if="Number(transactionData.discount_amount)"
> :label="__('Discount Amount')"
<div class="font-semibold"> v-model="transactionData.discount_amount"
{{ __('Coupon (if applied)') }} :disabled="true"
</div> />
<div class="grid grid-cols-3 gap-5 mt-5"> <FormControl
<Link v-if="Number(transactionData.gst_amount)"
:label="__('Coupon')" :label="__('GST Amount')"
v-model="transactionData.coupon" v-model="transactionData.gst_amount"
doctype="LMS Coupon" :disabled="true"
:disabled="true" />
/> <FormControl
<FormControl v-if="Number(transactionData.discount_amount) || Number(transactionData.gst_amount)"
:label="__('Discount Type')" :label="__('Total Amount')"
v-model="transactionData.discount_type" v-model="transactionData.total_amount"
:disabled="true" :disabled="true"
/> />
<FormControl
v-if="transactionData.discount_type === 'Percent'"
:label="__('Discount Percent')"
v-model="transactionData.discount_percent"
:disabled="true"
/>
<FormControl
v-else
:label="__('Discount Amount')"
v-model="transactionData.discount_amount"
:disabled="true"
/>
</div>
</div> </div>
<div class="grid grid-cols-3 gap-5 mt-5"> <div class="grid grid-cols-3 gap-5 mt-5">
@@ -114,6 +103,13 @@
v-model="transactionData.payment_id" v-model="transactionData.payment_id"
/> />
</div> </div>
<div class="grid grid-cols-3 gap-5 mt-5">
<FormControl
:label="__('Order ID')"
v-model="transactionData.order_id"
/>
</div>
</div> </div>
</template> </template>
<template #actions="{ close }"> <template #actions="{ close }">
@@ -73,7 +73,7 @@
:disabled="true" :disabled="true"
/> />
<div v-else-if="column.key == 'amount'"> <div v-else-if="column.key == 'amount'">
{{ getCurrencySymbol(row['currency']) }} {{ row[column.key] }} {{ getCurrencySymbol(row['currency']) }} {{ row['total_amount'] }}
</div> </div>
<div v-else class="leading-5 text-sm"> <div v-else class="leading-5 text-sm">
{{ row[column.key] }} {{ row[column.key] }}
@@ -153,6 +153,7 @@ const transactions = createListResource({
'payment_for_certificate', 'payment_for_certificate',
'currency', 'currency',
'amount', 'amount',
'total_amount',
'order_id', 'order_id',
'payment_id', 'payment_id',
'gstin', 'gstin',
+14 -9
View File
@@ -35,6 +35,10 @@
{{ orderSummary.data.original_amount_formatted }} {{ orderSummary.data.original_amount_formatted }}
</div> </div>
</div> </div>
<div v-if="orderSummary.data.discount_amount" class="flex items-center justify-between mt-2">
<div class="text-ink-gray-5">{{ __('Discount') }}</div>
<div>-{{ orderSummary.data.discount_amount_formatted }}</div>
</div>
<div <div
v-if="orderSummary.data.gst_applied" v-if="orderSummary.data.gst_applied"
class="flex items-center justify-between mt-2" class="flex items-center justify-between mt-2"
@@ -46,10 +50,6 @@
{{ orderSummary.data.gst_amount_formatted }} {{ orderSummary.data.gst_amount_formatted }}
</div> </div>
</div> </div>
<div v-if="orderSummary.data.discount_amount" class="flex items-center justify-between mt-2">
<div class="text-ink-gray-5">{{ __('Discount') }}</div>
<div>-{{ orderSummary.data.discount_amount_formatted }}</div>
</div>
<div <div
class="flex items-center justify-between border-t border-outline-gray-3 pt-4 mt-2" class="flex items-center justify-between border-t border-outline-gray-3 pt-4 mt-2"
> >
@@ -63,9 +63,9 @@
<div class="mb-5"> <div class="mb-5">
<div class="flex items-center gap-3 mt-2 flex-wrap md:flex-nowrap" v-if="props.type !== 'certificate'"> <div class="flex items-center gap-3 mt-2 flex-wrap md:flex-nowrap" v-if="props.type !== 'certificate'">
<span class="text-ink-gray-5 text-xs shrink-0">{{ __('Coupon') }}</span> <span class="text-ink-gray-5 text-xs shrink-0">{{ __('Coupon') }}</span>
<FormControl class="flex-1 min-w-0 [&_input]:!bg-[#fefefe]" v-model="couponCode" @input="couponCode = $event.target.value.toUpperCase()"/> <FormControl class="flex-1 min-w-0 [&_input]:!bg-[#fefefe]" v-model="couponCode" :disabled="appliedCoupon" @input="couponCode = $event.target.value.toUpperCase()"/>
<Button @click="applyCouponCode" variant="outline">{{ __('Apply') }}</Button> <Button v-if="!appliedCoupon" @click="applyCouponCode" variant="outline">{{ __('Apply') }}</Button>
<Button v-if="appliedCoupon" @click="removeCoupon" variant="subtle">{{ __('Remove') }}</Button> <Button v-if="appliedCoupon" @click="removeCoupon" variant="subtle" class="bg-red-200"><X class="h-4.5 w-4.5" /></Button>
</div> </div>
</div> </div>
</div> </div>
@@ -174,6 +174,7 @@ import { reactive, inject, onMounted, computed, ref } from 'vue'
import { sessionStore } from '../stores/session' import { sessionStore } from '../stores/session'
import Link from '@/components/Controls/Link.vue' import Link from '@/components/Controls/Link.vue'
import NotPermitted from '@/components/NotPermitted.vue' import NotPermitted from '@/components/NotPermitted.vue'
import { X } from 'lucide-vue-next'
const user = inject('$user') const user = inject('$user')
const { brand } = sessionStore() const { brand } = sessionStore()
@@ -239,6 +240,7 @@ const applyCoupon = createResource({
}, },
onSuccess(data) { onSuccess(data) {
orderSummary.data = data orderSummary.data = data
console.log('orderSummary.data - ', orderSummary.data)
appliedCoupon.value = couponCode.value appliedCoupon.value = couponCode.value
toast.success(__('Coupon applied')) toast.success(__('Coupon applied'))
}, },
@@ -266,18 +268,20 @@ const setBillingDetails = (data) => {
const paymentLink = createResource({ const paymentLink = createResource({
url: 'lms.lms.payments.get_payment_link', url: 'lms.lms.payments.get_payment_link',
makeParams(values) { makeParams(values) {
return { let data={
doctype: props.type == 'batch' ? 'LMS Batch' : 'LMS Course', doctype: props.type == 'batch' ? 'LMS Batch' : 'LMS Course',
docname: props.name, docname: props.name,
title: orderSummary.data.title, title: orderSummary.data.title,
amount: orderSummary.data.original_amount, amount: orderSummary.data.original_amount,
total_amount: orderSummary.data.amount, discount_amount: orderSummary.data.discount_amount || 0,
gst_amount: orderSummary.data.gst_applied || 0,
currency: orderSummary.data.currency, currency: orderSummary.data.currency,
address: billingDetails, address: billingDetails,
redirect_to: redirectTo.value, redirect_to: redirectTo.value,
payment_for_certificate: props.type == 'certificate', payment_for_certificate: props.type == 'certificate',
coupon_code: appliedCoupon.value, coupon_code: appliedCoupon.value,
} }
return data
}, },
}) })
@@ -308,6 +312,7 @@ function applyCouponCode() {
function removeCoupon() { function removeCoupon() {
appliedCoupon.value = null appliedCoupon.value = null
couponCode.value = ''
orderSummary.reload() orderSummary.reload()
} }
+38 -18
View File
@@ -9,14 +9,15 @@ from frappe.utils import nowdate
class LMSCoupon(Document): class LMSCoupon(Document):
def validate(self): def validate(self):
# Normalize code to uppercase and strip spaces
if self.code: if self.code:
self.code = self.code.strip().upper() self.code = self.code.strip().upper()
if not self.code: if not self.code:
frappe.throw(_("Coupon code is required.")) frappe.throw(_("Coupon code is required"))
if len(self.code) < 6:
frappe.throw(_("Coupon code must be atleast 6 characters"))
# Ensure uniqueness of code (case-insensitive)
if self.name: if self.name:
existing = frappe.db.exists( existing = frappe.db.exists(
"LMS Coupon", "LMS Coupon",
@@ -27,30 +28,49 @@ class LMSCoupon(Document):
) )
else: else:
existing = frappe.db.exists("LMS Coupon", {"code": self.code}) existing = frappe.db.exists("LMS Coupon", {"code": self.code})
if existing: if existing:
frappe.throw(_("Coupon code already exists.")) frappe.throw(_("Coupon code is already taken. Use a different one"))
if not self.discount_type: if not self.discount_type:
frappe.throw(_("Discount type is required.")) frappe.throw(_("Discount type is required"))
if self.discount_type == "Percent": if self.discount_type == "Percent":
if self.percent_off is None: if not self.percent_off or self.percent_off == "":
frappe.throw(_("Percent Off is required for Percent discount type.")) frappe.throw(_("Discount percentage is required"))
if not (0 < float(self.percent_off) <= 100): try:
frappe.throw(_("Percent Off must be between 1 and 100.")) percent_value = float(self.percent_off)
# Clear the other field to avoid confusion if not (0 < percent_value <= 100):
frappe.throw(_("Discount percentage must be between 1 and 100"))
except (ValueError, TypeError):
frappe.throw(_("Discount percentage must be a valid number"))
self.amount_off = None self.amount_off = None
if self.discount_type == "Amount": if self.discount_type == "Amount":
if self.amount_off is None: if not self.amount_off or self.amount_off == "":
frappe.throw(_("Amount Off is required for Amount discount type.")) frappe.throw(_("Discount amount is required"))
if float(self.amount_off) < 0: try:
frappe.throw(_("Amount Off cannot be negative.")) amount_value = float(self.amount_off)
# Clear the other field if amount_value < 0:
frappe.throw(_("Discount amount cannot be negative"))
except (ValueError, TypeError):
frappe.throw(_("Discount amount must be a valid number"))
self.percent_off = None self.percent_off = None
if self.usage_limit is not None and int(self.usage_limit) < 0: if self.usage_limit is not None and self.usage_limit != "":
frappe.throw(_("Usage limit cannot be negative.")) try:
usage_value = int(self.usage_limit)
if usage_value < 0:
frappe.throw(_("Usage limit cannot be negative"))
except (ValueError, TypeError):
frappe.throw(_("Usage limit must be a valid number"))
if self.expires_on and str(self.expires_on) < nowdate(): if self.expires_on and str(self.expires_on) < nowdate():
frappe.throw(_("Expiry date cannot be in the past.")) frappe.throw(_("Expiry date cannot be in the past"))
if not self.get("applicable_items") or len(self.get("applicable_items")) == 0:
frappe.throw(_("Please select atleast one course or batch"))
for item in self.get("applicable_items"):
if not item.get("reference_name"):
frappe.throw(_("Please select a valid course or batch"))
+19 -22
View File
@@ -19,10 +19,9 @@
"currency", "currency",
"amount", "amount",
"coupon", "coupon",
"discount_type",
"discount_percent",
"discount_amount", "discount_amount",
"amount_with_gst", "gst_amount",
"total_amount",
"column_break_yxpl", "column_break_yxpl",
"order_id", "order_id",
"payment_id", "payment_id",
@@ -57,17 +56,6 @@
"label": "Coupon", "label": "Coupon",
"options": "LMS Coupon" "options": "LMS Coupon"
}, },
{
"fieldname": "discount_type",
"fieldtype": "Select",
"label": "Discount Type",
"options": "\nPercent\nAmount"
},
{
"fieldname": "discount_percent",
"fieldtype": "Percent",
"label": "Discount Percent"
},
{ {
"fieldname": "discount_amount", "fieldname": "discount_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
@@ -144,12 +132,6 @@
"options": "User", "options": "User",
"reqd": 1 "reqd": 1
}, },
{
"depends_on": "eval:doc.currency == \"INR\";",
"fieldname": "amount_with_gst",
"fieldtype": "Currency",
"label": "Amount with GST"
},
{ {
"fieldname": "payment_for_document_type", "fieldname": "payment_for_document_type",
"fieldtype": "Select", "fieldtype": "Select",
@@ -176,6 +158,21 @@
"fieldtype": "Check", "fieldtype": "Check",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Payment for Certificate" "label": "Payment for Certificate"
},
{
"depends_on": "eval:doc.currency == \"INR\";",
"fieldname": "gst_amount",
"fieldtype": "Currency",
"label": "GST Amount",
"options": "currency"
},
{
"fieldname": "total_amount",
"fieldtype": "Currency",
"label": "Total Amount",
"options": "currency",
"read_only": 1,
"reqd": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
@@ -189,8 +186,8 @@
"link_fieldname": "payment" "link_fieldname": "payment"
} }
], ],
"modified": "2025-09-23 11:04:00.462274", "modified": "2025-10-13 15:25:56.127625",
"modified_by": "sayali@frappe.io", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Payment", "name": "LMS Payment",
"owner": "Administrator", "owner": "Administrator",
+6 -3
View File
@@ -5,12 +5,15 @@ import frappe
from frappe import _ from frappe import _
from frappe.email.doctype.email_template.email_template import get_email_template from frappe.email.doctype.email_template.email_template import get_email_template
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import add_days, nowdate from frappe.utils import add_days, nowdate, flt
class LMSPayment(Document): class LMSPayment(Document):
pass def validate(self):
amount = flt(self.amount or 0, self.precision("amount"))
discount = flt(self.discount_amount or 0, self.precision("discount_amount"))
gst = flt(self.gst_amount or 0, self.precision("gst_amount"))
self.total_amount = flt(amount - discount + gst, self.precision("total_amount"))
def send_payment_reminder(): def send_payment_reminder():
outgoing_email_account = frappe.get_cached_value( outgoing_email_account = frappe.get_cached_value(
+52 -54
View File
@@ -1,6 +1,5 @@
import frappe import frappe
def get_payment_gateway(): def get_payment_gateway():
return frappe.db.get_single_value("LMS Settings", "payment_gateway") return frappe.db.get_single_value("LMS Settings", "payment_gateway")
@@ -19,49 +18,46 @@ def validate_currency(payment_gateway, currency):
@frappe.whitelist() @frappe.whitelist()
def get_payment_link( def get_payment_link(
doctype, doctype,
docname, docname,
title, title,
amount, amount,
total_amount, discount_amount,
currency, gst_amount,
address, currency,
redirect_to, address,
payment_for_certificate, redirect_to,
coupon_code=None, payment_for_certificate,
coupon_code=None,
): ):
payment_gateway = get_payment_gateway() payment_gateway = get_payment_gateway()
address = frappe._dict(address) address = frappe._dict(address)
amount_with_gst = total_amount if total_amount != amount else 0
coupon_context = None coupon_context = None
# Coupon application only for courses/batches if doctype in ["LMS Course", "LMS Batch"] and coupon_code:
if doctype in ["LMS Course", "LMS Batch"] and coupon_code: try:
try: from lms.lms.utils import apply_coupon
from lms.lms.utils import apply_coupon coupon_context = apply_coupon(doctype, docname, coupon_code)
except Exception:
pass
applied = apply_coupon(doctype, docname, coupon_code) payment = record_payment(
# Override total_amount based on validated coupon calculation address,
total_amount = applied.get("amount", total_amount) doctype,
coupon_context = applied docname,
except Exception: amount,
# Ignore coupon errors here; frontend handles validation currency,
pass discount_amount,
gst_amount,
payment = record_payment( payment_for_certificate,
address, coupon_context,
doctype, )
docname,
amount,
currency,
amount_with_gst,
payment_for_certificate,
coupon_context,
)
controller = get_controller(payment_gateway) controller = get_controller(payment_gateway)
payment_details = { payment_details = {
"amount": total_amount, "amount": amount - discount_amount + gst_amount,
"discount_amount": discount_amount,
"gst_amount": gst_amount,
"title": f"Payment for {doctype} {title} {docname}", "title": f"Payment for {doctype} {title} {docname}",
"description": f"{address.billing_name}'s payment for {title}", "description": f"{address.billing_name}'s payment for {title}",
"reference_doctype": doctype, "reference_doctype": doctype,
@@ -83,14 +79,15 @@ def get_payment_link(
def record_payment( def record_payment(
address, address,
doctype, doctype,
docname, docname,
amount, amount,
currency, currency,
amount_with_gst=0, discount_amount=0,
payment_for_certificate=0, gst_amount=0,
coupon_context=None, payment_for_certificate=0,
coupon_context=None,
): ):
address = frappe._dict(address) address = frappe._dict(address)
address_name = save_address(address) address_name = save_address(address)
@@ -103,7 +100,8 @@ def record_payment(
"address": address_name, "address": address_name,
"amount": amount, "amount": amount,
"currency": currency, "currency": currency,
"amount_with_gst": amount_with_gst, "discount_amount": discount_amount,
"gst_amount": gst_amount,
"gstin": address.gstin, "gstin": address.gstin,
"pan": address.pan, "pan": address.pan,
"source": address.source, "source": address.source,
@@ -112,15 +110,15 @@ def record_payment(
"payment_for_certificate": payment_for_certificate, "payment_for_certificate": payment_for_certificate,
} }
) )
if coupon_context: if coupon_context:
payment_doc.update( payment_doc.update(
{ {
"coupon": coupon_context.get("coupon"), "coupon": coupon_context.get("coupon"),
"discount_type": coupon_context.get("discount_type"), "discount_type": coupon_context.get("discount_type"),
"discount_percent": coupon_context.get("discount_percent"), "discount_percent": coupon_context.get("discount_percent"),
"discount_amount": coupon_context.get("discount_amount"), "discount_amount": coupon_context.get("discount_amount"),
} }
) )
payment_doc.save(ignore_permissions=True) payment_doc.save(ignore_permissions=True)
return payment_doc return payment_doc