feat: implement coupon code manage
ment with billing and transaction integration; bug fixes
This commit is contained in:
@@ -24,15 +24,28 @@
|
||||
:label="__('Discount Amount')"
|
||||
type="number"
|
||||
/>
|
||||
<FormControl v-model="doc.expires_on" :label="__('Expires On')" type="date" />
|
||||
<FormControl v-model="doc.usage_limit" :label="__('Usage Limit')" type="number" />
|
||||
<FormControl
|
||||
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')" />
|
||||
<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 v-for="(row, idx) in doc.applicable_items" :key="idx" class="flex gap-2 items-center">
|
||||
<FormControl v-model="row.reference_doctype" :label="__('Type')" type="select" :options="['LMS Course', 'LMS Batch']" />
|
||||
<Link :doctype="row.reference_doctype || 'LMS Course'" :label="__('Item')" :value="row.reference_name" @change="(opt) => (row.reference_name = opt)" />
|
||||
<div v-for="(row, idx) in doc.applicable_items" :key="idx" class="flex gap-2 items-end">
|
||||
<FormControl class="w-28" v-model="row.reference_doctype" :label="__('Type')" type="select" :options="[
|
||||
{ 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)">
|
||||
<X class="h-3 w-3" />
|
||||
</Button>
|
||||
@@ -135,7 +148,6 @@ function save() {
|
||||
emit('saved')
|
||||
},
|
||||
onError(err) {
|
||||
console.log('Save error:', err)
|
||||
toast.error(err.messages?.[0] || err.message || err)
|
||||
}
|
||||
})
|
||||
@@ -147,7 +159,6 @@ function save() {
|
||||
emit('saved')
|
||||
},
|
||||
onError(err) {
|
||||
console.log('Insert error:', err)
|
||||
toast.error(err.messages?.[0] || err.message || err)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<th class="text-left p-2">{{ __('Expires On') }}</th>
|
||||
<th class="text-left p-2">{{ __('Usage') }}</th>
|
||||
<th class="text-left p-2">{{ __('Active') }}</th>
|
||||
<th class="text-right p-2 w-8"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -43,6 +44,13 @@
|
||||
<Badge v-if="row.active" theme="green">{{ __('Enabled') }}</Badge>
|
||||
<Badge v-else theme="gray">{{ __('Disabled') }}</Badge>
|
||||
</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>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -52,11 +60,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Button, Badge, createListResource } from 'frappe-ui'
|
||||
import { ref } from 'vue'
|
||||
import { Plus } from 'lucide-vue-next'
|
||||
import { Button, Badge, createListResource, toast, call } from 'frappe-ui'
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
import { Plus, Trash2 } from 'lucide-vue-next'
|
||||
import CouponDetails from '@/components/Settings/CouponDetails.vue'
|
||||
|
||||
const app = getCurrentInstance()
|
||||
const { $dialog } = app.appContext.config.globalProperties
|
||||
|
||||
defineProps({
|
||||
label: String,
|
||||
description: String,
|
||||
@@ -78,6 +89,7 @@ const coupons = createListResource({
|
||||
'times_redeemed',
|
||||
'active',
|
||||
],
|
||||
auto: true,
|
||||
})
|
||||
|
||||
function openForm(id) {
|
||||
@@ -88,5 +100,31 @@ function openForm(id) {
|
||||
function onSaved() {
|
||||
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>
|
||||
|
||||
|
||||
@@ -67,43 +67,32 @@
|
||||
/>
|
||||
<FormControl :label="__('Amount')" v-model="transactionData.amount" />
|
||||
<FormControl
|
||||
:label="__('Order ID')"
|
||||
v-model="transactionData.order_id"
|
||||
v-if="transactionData.coupon"
|
||||
:label="__('Coupon Code')"
|
||||
v-model="transactionData.coupon"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="transactionData && (transactionData.coupon || transactionData.discount_amount || transactionData.discount_percent)"
|
||||
class="mt-10"
|
||||
>
|
||||
<div class="font-semibold">
|
||||
{{ __('Coupon (if applied)') }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-5 mt-5">
|
||||
<Link
|
||||
:label="__('Coupon')"
|
||||
v-model="transactionData.coupon"
|
||||
doctype="LMS Coupon"
|
||||
:disabled="true"
|
||||
/>
|
||||
<FormControl
|
||||
:label="__('Discount Type')"
|
||||
v-model="transactionData.discount_type"
|
||||
: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 class="grid grid-cols-3 gap-5 mt-5">
|
||||
<FormControl
|
||||
v-if="Number(transactionData.discount_amount)"
|
||||
:label="__('Discount Amount')"
|
||||
v-model="transactionData.discount_amount"
|
||||
:disabled="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="Number(transactionData.gst_amount)"
|
||||
:label="__('GST Amount')"
|
||||
v-model="transactionData.gst_amount"
|
||||
:disabled="true"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="Number(transactionData.discount_amount) || Number(transactionData.gst_amount)"
|
||||
:label="__('Total Amount')"
|
||||
v-model="transactionData.total_amount"
|
||||
:disabled="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-5">
|
||||
@@ -114,6 +103,13 @@
|
||||
v-model="transactionData.payment_id"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-5">
|
||||
<FormControl
|
||||
:label="__('Order ID')"
|
||||
v-model="transactionData.order_id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #actions="{ close }">
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
:disabled="true"
|
||||
/>
|
||||
<div v-else-if="column.key == 'amount'">
|
||||
{{ getCurrencySymbol(row['currency']) }} {{ row[column.key] }}
|
||||
{{ getCurrencySymbol(row['currency']) }} {{ row['total_amount'] }}
|
||||
</div>
|
||||
<div v-else class="leading-5 text-sm">
|
||||
{{ row[column.key] }}
|
||||
@@ -153,6 +153,7 @@ const transactions = createListResource({
|
||||
'payment_for_certificate',
|
||||
'currency',
|
||||
'amount',
|
||||
'total_amount',
|
||||
'order_id',
|
||||
'payment_id',
|
||||
'gstin',
|
||||
|
||||
Reference in New Issue
Block a user