chore: applied pre-commit hooks
This commit is contained in:
@@ -2,7 +2,15 @@
|
|||||||
<Dialog v-model="show" :options="{ title: dialogTitle, size: '3xl' }">
|
<Dialog v-model="show" :options="{ title: dialogTitle, size: '3xl' }">
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
<div class="grid grid-cols-2 gap-4 pt-4">
|
<div class="grid grid-cols-2 gap-4 pt-4">
|
||||||
<FormControl v-model="doc.code" :label="__('Coupon Code')" :required="true" pattern="^[A-Za-z0-9]+$" minlength="6" @beforeinput="handleCodeInput" @input="doc.code = $event.target.value.toUpperCase()" />
|
<FormControl
|
||||||
|
v-model="doc.code"
|
||||||
|
:label="__('Coupon Code')"
|
||||||
|
:required="true"
|
||||||
|
pattern="^[A-Za-z0-9]+$"
|
||||||
|
minlength="6"
|
||||||
|
@beforeinput="handleCodeInput"
|
||||||
|
@input="doc.code = $event.target.value.toUpperCase()"
|
||||||
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="doc.discount_type"
|
v-model="doc.discount_type"
|
||||||
:label="__('Discount Type')"
|
:label="__('Discount Type')"
|
||||||
@@ -38,14 +46,33 @@
|
|||||||
/>
|
/>
|
||||||
<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-md font-medium text-ink-gray-7 mb-1 mt-2">{{ __('Select Courses/Batches') }}<span class="text-ink-red-3">*</span></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-end">
|
<div
|
||||||
<FormControl class="w-28" v-model="row.reference_doctype" :label="__('Type')" type="select" :options="[
|
v-for="(row, idx) in doc.applicable_items"
|
||||||
{ label: 'Course ', value: 'LMS Course' },
|
:key="idx"
|
||||||
{ label: 'Batch ', value: 'LMS Batch' }
|
class="flex gap-2 items-end"
|
||||||
]" />
|
>
|
||||||
<Link class="min-w-40" :doctype="row.reference_doctype || 'LMS Course'" :label="__('Name')" :value="row.reference_name" @change="(opt) => (row.reference_name = opt)" />
|
<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)">
|
<Button variant="subtle" @click="removeRow(idx)">
|
||||||
<X class="h-3 w-3" />
|
<X class="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -60,7 +87,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<div class="pb-5 float-right space-x-2">
|
<div class="pb-5 float-right space-x-2">
|
||||||
<Button variant="outline" @click="show = false">{{ __('Cancel') }}</Button>
|
<Button variant="outline" @click="show = false">{{
|
||||||
|
__('Cancel')
|
||||||
|
}}</Button>
|
||||||
<Button variant="solid" @click="save">{{ __('Save') }}</Button>
|
<Button variant="solid" @click="save">{{ __('Save') }}</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -87,7 +116,9 @@ const doc = ref({
|
|||||||
applicable_items: [],
|
applicable_items: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const dialogTitle = computed(() => (props.couponId === 'new' ? __('New Coupon') : __('Edit Coupon')))
|
const dialogTitle = computed(() =>
|
||||||
|
props.couponId === 'new' ? __('New Coupon') : __('Edit Coupon')
|
||||||
|
)
|
||||||
|
|
||||||
const getDoc = createResource({
|
const getDoc = createResource({
|
||||||
url: 'frappe.client.get',
|
url: 'frappe.client.get',
|
||||||
@@ -106,14 +137,22 @@ watch(
|
|||||||
if (props.couponId && props.couponId !== 'new') {
|
if (props.couponId && props.couponId !== 'new') {
|
||||||
getDoc.submit()
|
getDoc.submit()
|
||||||
} else {
|
} else {
|
||||||
doc.value = { code: '', discount_type: 'Percent', active: 1, applicable_items: [] }
|
doc.value = {
|
||||||
|
code: '',
|
||||||
|
discount_type: 'Percent',
|
||||||
|
active: 1,
|
||||||
|
applicable_items: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function addRow() {
|
function addRow() {
|
||||||
doc.value.applicable_items.push({ reference_doctype: 'LMS Course', reference_name: null })
|
doc.value.applicable_items.push({
|
||||||
|
reference_doctype: 'LMS Course',
|
||||||
|
reference_name: null,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function removeRow(idx) {
|
function removeRow(idx) {
|
||||||
doc.value.applicable_items.splice(idx, 1)
|
doc.value.applicable_items.splice(idx, 1)
|
||||||
@@ -141,28 +180,33 @@ function handleCodeInput(event) {
|
|||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
if (props.couponId && props.couponId !== 'new') {
|
if (props.couponId && props.couponId !== 'new') {
|
||||||
saveDoc.submit({}, {
|
saveDoc.submit(
|
||||||
onSuccess() {
|
{},
|
||||||
toast.success(__('Saved'))
|
{
|
||||||
show.value = false
|
onSuccess() {
|
||||||
emit('saved')
|
toast.success(__('Saved'))
|
||||||
},
|
show.value = false
|
||||||
onError(err) {
|
emit('saved')
|
||||||
toast.error(err.messages?.[0] || err.message || err)
|
},
|
||||||
|
onError(err) {
|
||||||
|
toast.error(err.messages?.[0] || err.message || err)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
} else {
|
} else {
|
||||||
insertDoc.submit({}, {
|
insertDoc.submit(
|
||||||
onSuccess() {
|
{},
|
||||||
toast.success(__('Saved'))
|
{
|
||||||
show.value = false
|
onSuccess() {
|
||||||
emit('saved')
|
toast.success(__('Saved'))
|
||||||
},
|
show.value = false
|
||||||
onError(err) {
|
emit('saved')
|
||||||
toast.error(err.messages?.[0] || err.message || err)
|
},
|
||||||
|
onError(err) {
|
||||||
|
toast.error(err.messages?.[0] || err.message || err)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -27,36 +27,49 @@
|
|||||||
<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>
|
<th class="text-right p-2 w-8"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="row in coupons.data" :key="row.name" class="hover:bg-surface-gray-2 cursor-pointer" @click="openForm(row.name)">
|
<tr
|
||||||
|
v-for="row in coupons.data"
|
||||||
|
:key="row.name"
|
||||||
|
class="hover:bg-surface-gray-2 cursor-pointer"
|
||||||
|
@click="openForm(row.name)"
|
||||||
|
>
|
||||||
<td class="p-2">{{ row.code }}</td>
|
<td class="p-2">{{ row.code }}</td>
|
||||||
<td class="p-2">{{ row.discount_type }}</td>
|
<td class="p-2">{{ row.discount_type }}</td>
|
||||||
<td class="p-2">
|
<td class="p-2">
|
||||||
<span v-if="row.discount_type === 'Percent'">{{ row.percent_off }}%</span>
|
<span v-if="row.discount_type === 'Percent'"
|
||||||
|
>{{ row.percent_off }}%</span
|
||||||
|
>
|
||||||
<span v-else>{{ row.amount_off }}</span>
|
<span v-else>{{ row.amount_off }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="p-2">{{ row.expires_on || '-' }}</td>
|
<td class="p-2">{{ row.expires_on || '-' }}</td>
|
||||||
<td class="p-2">{{ row.times_redeemed }}/{{ row.usage_limit || '∞' }}</td>
|
<td class="p-2">
|
||||||
|
{{ row.times_redeemed }}/{{ row.usage_limit || '∞' }}
|
||||||
|
</td>
|
||||||
<td class="p-2">
|
<td class="p-2">
|
||||||
<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>
|
<td class="p-2 text-right" @click.stop>
|
||||||
<Button variant="ghost" @click="confirmDelete(row)">
|
<Button variant="ghost" @click="confirmDelete(row)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Trash2 class="h-4 w-4 stroke-1.5 text-ink-red-4" />
|
<Trash2 class="h-4 w-4 stroke-1.5 text-ink-red-4" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CouponDetails v-model="showDialog" :coupon-id="selected" @saved="onSaved" />
|
<CouponDetails
|
||||||
|
v-model="showDialog"
|
||||||
|
:coupon-id="selected"
|
||||||
|
@saved="onSaved"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -104,7 +117,9 @@ function onSaved() {
|
|||||||
function confirmDelete(row) {
|
function confirmDelete(row) {
|
||||||
$dialog({
|
$dialog({
|
||||||
title: __('Delete this coupon?'),
|
title: __('Delete this coupon?'),
|
||||||
message: __('This will permanently delete the coupon and the code will no longer work. Are you sure?'),
|
message: __(
|
||||||
|
'This will permanently delete the coupon and the code will no longer work. Are you sure?'
|
||||||
|
),
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
label: __('Delete'),
|
label: __('Delete'),
|
||||||
@@ -127,4 +142,3 @@ function trashCoupon(name, close) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,10 @@
|
|||||||
:disabled="true"
|
:disabled="true"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-if="Number(transactionData.discount_amount) || Number(transactionData.gst_amount)"
|
v-if="
|
||||||
|
Number(transactionData.discount_amount) ||
|
||||||
|
Number(transactionData.gst_amount)
|
||||||
|
"
|
||||||
:label="__('Total Amount')"
|
:label="__('Total Amount')"
|
||||||
v-model="transactionData.total_amount"
|
v-model="transactionData.total_amount"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
:disabled="true"
|
:disabled="true"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="column.key == 'amount'">
|
<div v-else-if="column.key == 'amount'">
|
||||||
{{ getCurrencySymbol(row['currency']) }} {{ row['total_amount'] }}
|
{{ 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] }}
|
||||||
|
|||||||
@@ -35,7 +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
|
||||||
|
v-if="orderSummary.data.discount_amount"
|
||||||
|
class="flex items-center justify-between mt-2"
|
||||||
|
>
|
||||||
<div class="text-ink-gray-5">{{ __('Discount') }}</div>
|
<div class="text-ink-gray-5">{{ __('Discount') }}</div>
|
||||||
<div>-{{ orderSummary.data.discount_amount_formatted }}</div>
|
<div>-{{ orderSummary.data.discount_amount_formatted }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,11 +64,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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
|
||||||
<span class="text-ink-gray-5 text-xs shrink-0">{{ __('Coupon') }}</span>
|
class="flex items-center gap-3 mt-2 flex-wrap md:flex-nowrap"
|
||||||
<FormControl class="flex-1 min-w-0 [&_input]:!bg-[#fefefe]" v-model="couponCode" :disabled="appliedCoupon" @input="couponCode = $event.target.value.toUpperCase()"/>
|
v-if="props.type !== 'certificate'"
|
||||||
<Button v-if="!appliedCoupon" @click="applyCouponCode" variant="outline">{{ __('Apply') }}</Button>
|
>
|
||||||
<Button v-if="appliedCoupon" @click="removeCoupon" variant="subtle" class="bg-red-200"><X class="h-4.5 w-4.5" /></Button>
|
<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"
|
||||||
|
:disabled="appliedCoupon"
|
||||||
|
@input="couponCode = $event.target.value.toUpperCase()"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="!appliedCoupon"
|
||||||
|
@click="applyCouponCode"
|
||||||
|
variant="outline"
|
||||||
|
>{{ __('Apply') }}</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>
|
||||||
@@ -268,7 +292,7 @@ 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) {
|
||||||
let data={
|
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,
|
||||||
|
|||||||
@@ -2,75 +2,75 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
|
||||||
class LMSCoupon(Document):
|
class LMSCoupon(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
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:
|
if len(self.code) < 6:
|
||||||
frappe.throw(_("Coupon code must be atleast 6 characters"))
|
frappe.throw(_("Coupon code must be atleast 6 characters"))
|
||||||
|
|
||||||
if self.name:
|
if self.name:
|
||||||
existing = frappe.db.exists(
|
existing = frappe.db.exists(
|
||||||
"LMS Coupon",
|
"LMS Coupon",
|
||||||
{
|
{
|
||||||
"code": self.code,
|
"code": self.code,
|
||||||
"name": ["!=", self.name],
|
"name": ["!=", self.name],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
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 is already taken. Use a different one"))
|
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 not self.percent_off or self.percent_off == "":
|
if not self.percent_off or self.percent_off == "":
|
||||||
frappe.throw(_("Discount percentage is required"))
|
frappe.throw(_("Discount percentage is required"))
|
||||||
try:
|
try:
|
||||||
percent_value = float(self.percent_off)
|
percent_value = float(self.percent_off)
|
||||||
if not (0 < percent_value <= 100):
|
if not (0 < percent_value <= 100):
|
||||||
frappe.throw(_("Discount percentage must be between 1 and 100"))
|
frappe.throw(_("Discount percentage must be between 1 and 100"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
frappe.throw(_("Discount percentage must be a valid number"))
|
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 not self.amount_off or self.amount_off == "":
|
if not self.amount_off or self.amount_off == "":
|
||||||
frappe.throw(_("Discount amount is required"))
|
frappe.throw(_("Discount amount is required"))
|
||||||
try:
|
try:
|
||||||
amount_value = float(self.amount_off)
|
amount_value = float(self.amount_off)
|
||||||
if amount_value < 0:
|
if amount_value < 0:
|
||||||
frappe.throw(_("Discount amount cannot be negative"))
|
frappe.throw(_("Discount amount cannot be negative"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
frappe.throw(_("Discount amount must be a valid number"))
|
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 self.usage_limit != "":
|
if self.usage_limit is not None and self.usage_limit != "":
|
||||||
try:
|
try:
|
||||||
usage_value = int(self.usage_limit)
|
usage_value = int(self.usage_limit)
|
||||||
if usage_value < 0:
|
if usage_value < 0:
|
||||||
frappe.throw(_("Usage limit cannot be negative"))
|
frappe.throw(_("Usage limit cannot be negative"))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
frappe.throw(_("Usage limit must be a valid number"))
|
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:
|
if not self.get("applicable_items") or len(self.get("applicable_items")) == 0:
|
||||||
frappe.throw(_("Please select atleast one course or batch"))
|
frappe.throw(_("Please select atleast one course or batch"))
|
||||||
|
|
||||||
for item in self.get("applicable_items"):
|
for item in self.get("applicable_items"):
|
||||||
if not item.get("reference_name"):
|
if not item.get("reference_name"):
|
||||||
frappe.throw(_("Please select a valid course or batch"))
|
frappe.throw(_("Please select a valid course or batch"))
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests import IntegrationTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
|
|
||||||
# On IntegrationTestCase, the doctype test records and all
|
# On IntegrationTestCase, the doctype test records and all
|
||||||
# link-field test record dependencies are recursively loaded
|
# link-field test record dependencies are recursively loaded
|
||||||
# Use these module variables to add/remove to/from that list
|
# Use these module variables to add/remove to/from that list
|
||||||
@@ -12,7 +11,6 @@ EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
|||||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class IntegrationTestLMSCoupon(IntegrationTestCase):
|
class IntegrationTestLMSCoupon(IntegrationTestCase):
|
||||||
"""
|
"""
|
||||||
Integration tests for LMSCoupon.
|
Integration tests for LMSCoupon.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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, flt
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
|
|
||||||
class LMSPayment(Document):
|
class LMSPayment(Document):
|
||||||
@@ -15,6 +15,7 @@ class LMSPayment(Document):
|
|||||||
gst = flt(self.gst_amount or 0, self.precision("gst_amount"))
|
gst = flt(self.gst_amount or 0, self.precision("gst_amount"))
|
||||||
self.total_amount = flt(amount - discount + gst, self.precision("total_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(
|
||||||
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
|
"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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")
|
||||||
|
|
||||||
@@ -18,17 +19,17 @@ 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,
|
||||||
discount_amount,
|
discount_amount,
|
||||||
gst_amount,
|
gst_amount,
|
||||||
currency,
|
currency,
|
||||||
address,
|
address,
|
||||||
redirect_to,
|
redirect_to,
|
||||||
payment_for_certificate,
|
payment_for_certificate,
|
||||||
coupon_code=None,
|
coupon_code=None,
|
||||||
):
|
):
|
||||||
payment_gateway = get_payment_gateway()
|
payment_gateway = get_payment_gateway()
|
||||||
address = frappe._dict(address)
|
address = frappe._dict(address)
|
||||||
@@ -37,21 +38,22 @@ def get_payment_link(
|
|||||||
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)
|
coupon_context = apply_coupon(doctype, docname, coupon_code)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
payment = record_payment(
|
payment = record_payment(
|
||||||
address,
|
address,
|
||||||
doctype,
|
doctype,
|
||||||
docname,
|
docname,
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
discount_amount,
|
discount_amount,
|
||||||
gst_amount,
|
gst_amount,
|
||||||
payment_for_certificate,
|
payment_for_certificate,
|
||||||
coupon_context,
|
coupon_context,
|
||||||
)
|
)
|
||||||
controller = get_controller(payment_gateway)
|
controller = get_controller(payment_gateway)
|
||||||
|
|
||||||
payment_details = {
|
payment_details = {
|
||||||
@@ -79,15 +81,15 @@ def get_payment_link(
|
|||||||
|
|
||||||
|
|
||||||
def record_payment(
|
def record_payment(
|
||||||
address,
|
address,
|
||||||
doctype,
|
doctype,
|
||||||
docname,
|
docname,
|
||||||
amount,
|
amount,
|
||||||
currency,
|
currency,
|
||||||
discount_amount=0,
|
discount_amount=0,
|
||||||
gst_amount=0,
|
gst_amount=0,
|
||||||
payment_for_certificate=0,
|
payment_for_certificate=0,
|
||||||
coupon_context=None,
|
coupon_context=None,
|
||||||
):
|
):
|
||||||
address = frappe._dict(address)
|
address = frappe._dict(address)
|
||||||
address_name = save_address(address)
|
address_name = save_address(address)
|
||||||
|
|||||||
112
lms/lms/utils.py
112
lms/lms/utils.py
@@ -953,74 +953,74 @@ def get_current_exchange_rate(source, target="USD"):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def apply_coupon(doctype, docname, code, country=None):
|
def apply_coupon(doctype, docname, code, country=None):
|
||||||
# Validate doctype
|
# Validate doctype
|
||||||
if doctype not in ["LMS Course", "LMS Batch"]:
|
if doctype not in ["LMS Course", "LMS Batch"]:
|
||||||
frappe.throw(_("Invalid doctype for coupon application."))
|
frappe.throw(_("Invalid doctype for coupon application."))
|
||||||
|
|
||||||
if not code:
|
if not code:
|
||||||
frappe.throw(_("Coupon code is required."))
|
frappe.throw(_("Coupon code is required."))
|
||||||
|
|
||||||
summary = get_order_summary(doctype, docname, country)
|
summary = get_order_summary(doctype, docname, country)
|
||||||
|
|
||||||
base_amount = summary.original_amount
|
base_amount = summary.original_amount
|
||||||
currency = summary.currency
|
currency = summary.currency
|
||||||
|
|
||||||
# Fetch coupon case-insensitively
|
# Fetch coupon case-insensitively
|
||||||
coupon_name = frappe.db.get_value("LMS Coupon", {"code": code.strip().upper(), "active": 1}, "name")
|
coupon_name = frappe.db.get_value("LMS Coupon", {"code": code.strip().upper(), "active": 1}, "name")
|
||||||
if not coupon_name:
|
if not coupon_name:
|
||||||
frappe.throw(_("Invalid or inactive coupon code."))
|
frappe.throw(_("Invalid or inactive coupon code."))
|
||||||
|
|
||||||
coupon = frappe.get_doc("LMS Coupon", coupon_name)
|
coupon = frappe.get_doc("LMS Coupon", coupon_name)
|
||||||
|
|
||||||
# Expiry
|
# Expiry
|
||||||
if coupon.expires_on and getdate(coupon.expires_on) < getdate():
|
if coupon.expires_on and getdate(coupon.expires_on) < getdate():
|
||||||
frappe.throw(_("This coupon has expired."))
|
frappe.throw(_("This coupon has expired."))
|
||||||
|
|
||||||
# Usage limit
|
# Usage limit
|
||||||
if coupon.usage_limit and cint(coupon.times_redeemed) >= cint(coupon.usage_limit):
|
if coupon.usage_limit and cint(coupon.times_redeemed) >= cint(coupon.usage_limit):
|
||||||
frappe.throw(_("This coupon has reached its usage limit."))
|
frappe.throw(_("This coupon has reached its usage limit."))
|
||||||
|
|
||||||
# Applicability (if rows exist, must match; if none, applies to all)
|
# Applicability (if rows exist, must match; if none, applies to all)
|
||||||
applicable = True
|
applicable = True
|
||||||
if len(coupon.applicable_items):
|
if len(coupon.applicable_items):
|
||||||
applicable = any(
|
applicable = any(
|
||||||
(row.reference_doctype == doctype and row.reference_name == docname)
|
(row.reference_doctype == doctype and row.reference_name == docname)
|
||||||
for row in coupon.applicable_items
|
for row in coupon.applicable_items
|
||||||
)
|
)
|
||||||
if not applicable:
|
if not applicable:
|
||||||
frappe.throw(_("This coupon is not applicable to this item."))
|
frappe.throw(_("This coupon is not applicable to this item."))
|
||||||
|
|
||||||
# Compute discount before tax
|
# Compute discount before tax
|
||||||
discount_amount = 0
|
discount_amount = 0
|
||||||
if coupon.discount_type == "Percent":
|
if coupon.discount_type == "Percent":
|
||||||
discount_amount = cint(flt(base_amount) * flt(coupon.percent_off) / 100)
|
discount_amount = cint(flt(base_amount) * flt(coupon.percent_off) / 100)
|
||||||
else:
|
else:
|
||||||
discount_amount = min(flt(coupon.amount_off), flt(base_amount))
|
discount_amount = min(flt(coupon.amount_off), flt(base_amount))
|
||||||
|
|
||||||
subtotal = max(flt(base_amount) - flt(discount_amount), 0)
|
subtotal = max(flt(base_amount) - flt(discount_amount), 0)
|
||||||
|
|
||||||
gst_applied = 0
|
gst_applied = 0
|
||||||
final_amount = subtotal
|
final_amount = subtotal
|
||||||
if currency == "INR":
|
if currency == "INR":
|
||||||
final_amount, gst_applied = apply_gst(subtotal, country)
|
final_amount, gst_applied = apply_gst(subtotal, country)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": summary.title,
|
"title": summary.title,
|
||||||
"name": summary.name,
|
"name": summary.name,
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
"original_amount": base_amount,
|
"original_amount": base_amount,
|
||||||
"original_amount_formatted": fmt_money(base_amount, 0, currency),
|
"original_amount_formatted": fmt_money(base_amount, 0, currency),
|
||||||
"discount_amount": discount_amount,
|
"discount_amount": discount_amount,
|
||||||
"discount_amount_formatted": fmt_money(discount_amount, 0, currency),
|
"discount_amount_formatted": fmt_money(discount_amount, 0, currency),
|
||||||
"amount": final_amount,
|
"amount": final_amount,
|
||||||
"gst_applied": gst_applied,
|
"gst_applied": gst_applied,
|
||||||
"gst_amount_formatted": fmt_money(gst_applied, 0, currency) if gst_applied else None,
|
"gst_amount_formatted": fmt_money(gst_applied, 0, currency) if gst_applied else None,
|
||||||
"total_amount_formatted": fmt_money(final_amount, 0, currency),
|
"total_amount_formatted": fmt_money(final_amount, 0, currency),
|
||||||
"coupon": coupon.name,
|
"coupon": coupon.name,
|
||||||
"coupon_code": coupon.code,
|
"coupon_code": coupon.code,
|
||||||
"discount_type": coupon.discount_type,
|
"discount_type": coupon.discount_type,
|
||||||
"discount_percent": coupon.percent_off if coupon.discount_type == "Percent" else None,
|
"discount_percent": coupon.percent_off if coupon.discount_type == "Percent" else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
Reference in New Issue
Block a user