fix: polished the course certification flow

This commit is contained in:
Jannat Patel
2025-02-25 12:46:35 +05:30
parent 4f1dcbfb78
commit 63bf6a5574
9 changed files with 44 additions and 25 deletions

View File

@@ -104,10 +104,10 @@
{{ course.price }} {{ course.price }}
</div> </div>
<div <div
v-if="course.paid_certificate" v-if="course.paid_certificate || course.enable_certification"
class="text-xs text-ink-blue-3 bg-surface-blue-1 py-0.5 px-1 rounded-md" class="text-xs text-ink-blue-3 bg-surface-blue-1 py-0.5 px-1 rounded-md"
> >
{{ __('Paid Certificate') }} {{ __('Certification') }}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -115,13 +115,22 @@
{{ course.data.rating }} {{ __('Rating') }} {{ course.data.rating }} {{ __('Rating') }}
</span> </span>
</div> </div>
<div
v-if="course.data.enable_certification"
class="flex items-center font-semibold text-ink-gray-9"
>
<GraduationCap class="h-4 w-4 stroke-2" />
<span class="ml-2">
{{ __('Certificate of Completion') }}
</span>
</div>
<div <div
v-if="course.data.paid_certificate" v-if="course.data.paid_certificate"
class="flex items-center font-semibold text-ink-gray-9" class="flex items-center font-semibold text-ink-gray-9"
> >
<GraduationCap class="h-4 w-4 stroke-2" /> <GraduationCap class="h-4 w-4 stroke-2" />
<span class="ml-2"> <span class="ml-2">
{{ __('Paid Certificate') }} {{ __('Paid Certificate after Evaluation') }}
</span> </span>
</div> </div>
</div> </div>
@@ -131,7 +140,7 @@
<script setup> <script setup>
import { BookOpen, Users, Star, GraduationCap } from 'lucide-vue-next' import { BookOpen, Users, Star, GraduationCap } from 'lucide-vue-next'
import { computed, inject } from 'vue' import { computed, inject } from 'vue'
import { Button, createResource } from 'frappe-ui' import { Button, createResource, Tooltip } from 'frappe-ui'
import { showToast, formatAmount } from '@/utils/' import { showToast, formatAmount } from '@/utils/'
import { capture } from '@/telemetry' import { capture } from '@/telemetry'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'

View File

@@ -113,10 +113,10 @@ const props = defineProps({
const upcoming_evals = createResource({ const upcoming_evals = createResource({
url: 'lms.lms.utils.get_upcoming_evals', url: 'lms.lms.utils.get_upcoming_evals',
cache: ['upcoming_evals', user.data.name],
params: { params: {
student: user.data.name, student: user.data.name,
courses: props.courses.map((course) => course.course), courses: props.courses.map((course) => course.course),
batch: props.batch,
}, },
auto: true, auto: true,
}) })

View File

@@ -12,16 +12,11 @@
v-if="access.data?.access && orderSummary.data" v-if="access.data?.access && orderSummary.data"
class="pt-5 pb-10 mx-5" class="pt-5 pb-10 mx-5"
> >
<!-- <div class="mb-5">
<div class="text-lg font-semibold">
{{ __('Address') }}
</div>
</div> -->
<div class="flex flex-col lg:flex-row justify-between"> <div class="flex flex-col lg:flex-row justify-between">
<div <div
class="h-fit bg-surface-gray-2 rounded-md p-5 space-y-4 lg:order-last mb-10 lg:mt-10 text-sm font-medium lg:w-1/4" class="h-fit bg-surface-gray-2 rounded-md p-5 space-y-4 lg:order-last mb-10 lg:mt-10 font-medium lg:w-1/3"
> >
<div class="flex flex-col space-y-2"> <div class="flex items-baseline justify-between space-y-2">
<div class="text-ink-gray-5"> <div class="text-ink-gray-5">
{{ __('Payment for ') }} {{ type }}: {{ __('Payment for ') }} {{ type }}:
</div> </div>
@@ -243,17 +238,19 @@ const paymentLink = createResource({
total_amount: orderSummary.data.amount, total_amount: orderSummary.data.amount,
currency: orderSummary.data.currency, currency: orderSummary.data.currency,
address: billingDetails, address: billingDetails,
redirect_to: redirectTo, redirect_to: redirectTo.value,
payment_for_certificate: props.type == 'certificate', payment_for_certificate: props.type == 'certificate',
} }
}, },
}) })
const generatePaymentLink = () => { const generatePaymentLink = () => {
console.log('called')
paymentLink.submit( paymentLink.submit(
{}, {},
{ {
validate() { validate() {
console.log('validation start')
if (!billingDetails.source) { if (!billingDetails.source) {
return __('Please let us know where you heard about us from.') return __('Please let us know where you heard about us from.')
} }
@@ -332,6 +329,8 @@ const validateAddress = () => {
!states.includes(billingDetails.state) !states.includes(billingDetails.state)
) )
return 'Please enter a valid state with correct spelling and the first letter capitalized.' return 'Please enter a valid state with correct spelling and the first letter capitalized.'
console.log('validation address')
} }
const showError = (err) => { const showError = (err) => {

View File

@@ -5,7 +5,7 @@
<Breadcrumbs class="h-7" :items="breadcrumbs" /> <Breadcrumbs class="h-7" :items="breadcrumbs" />
</header> </header>
<div class="p-5"> <div class="p-5">
<div v-if="certificate.data"> <div v-if="certificate.data && Object.keys(certificate.data).length">
<div class="text-lg text-ink-gray-9 font-semibold mb-1"> <div class="text-lg text-ink-gray-9 font-semibold mb-1">
{{ __('Certification') }} {{ __('Certification') }}
</div> </div>
@@ -17,7 +17,7 @@
}} }}
</div> </div>
<div <div
class="border p-2 w-fit rounded-md space-y-2 hover:bg-surface-gray-1 cursor-pointer mt-5" class="border p-3 w-fit min-w-60 rounded-md space-y-2 hover:bg-surface-gray-1 cursor-pointer mt-5"
@click="openCertificate(certificate.data)" @click="openCertificate(certificate.data)"
> >
<div class="text-ink-gray-9 font-semibold"> <div class="text-ink-gray-9 font-semibold">
@@ -67,6 +67,7 @@ const certificate = createResource({
fieldname: ['name', 'template', 'issue_date'], fieldname: ['name', 'template', 'issue_date'],
}, },
auto: true, auto: true,
cache: [user.data?.name, props.courseName],
}) })
const fetchCourseDetails = () => { const fetchCourseDetails = () => {

View File

@@ -304,6 +304,7 @@ const course = reactive({
paid_certificate: false, paid_certificate: false,
course_price: '', course_price: '',
currency: '', currency: '',
evaluator: '',
}) })
onMounted(() => { onMounted(() => {

View File

@@ -59,7 +59,6 @@ class CourseEvaluator(Document):
@frappe.whitelist() @frappe.whitelist()
def get_schedule(course, date, batch=None): def get_schedule(course, date, batch=None):
print(batch)
evaluator = get_evaluator(course, batch) evaluator = get_evaluator(course, batch)
day = datetime.strptime(date, "%Y-%m-%d").strftime("%A") day = datetime.strptime(date, "%Y-%m-%d").strftime("%A")

View File

@@ -18,7 +18,7 @@ class LMSCourse(Document):
self.validate_video_link() self.validate_video_link()
self.validate_status() self.validate_status()
self.validate_payments_app() self.validate_payments_app()
self.validate_evaluator() self.validate_certification()
self.validate_amount_and_currency() self.validate_amount_and_currency()
self.image = validate_image(self.image) self.image = validate_image(self.image)
@@ -52,7 +52,12 @@ class LMSCourse(Document):
if "payments" not in installed_apps: if "payments" not in installed_apps:
frappe.throw(_("Please install the Payments app to create a paid courses.")) frappe.throw(_("Please install the Payments app to create a paid courses."))
def validate_evaluator(self): def validate_certification(self):
if self.enable_certification and self.paid_certificate:
frappe.throw(
_("A course cannot have both paid certificate and certificate of completion.")
)
if self.paid_certificate and not self.evaluator: if self.paid_certificate and not self.evaluator:
frappe.throw(_("Evaluator is required for paid certificates.")) frappe.throw(_("Evaluator is required for paid certificates."))

View File

@@ -869,15 +869,20 @@ def get_evaluator(course, batch=None):
@frappe.whitelist() @frappe.whitelist()
def get_upcoming_evals(student, courses): def get_upcoming_evals(student, courses, batch=None):
filters = {
"member": student,
"course": ["in", courses],
"date": [">=", frappe.utils.nowdate()],
"status": "Upcoming",
}
if batch:
filters["batch_name"] = batch
upcoming_evals = frappe.get_all( upcoming_evals = frappe.get_all(
"LMS Certificate Request", "LMS Certificate Request",
{ filters,
"member": student,
"course": ["in", courses],
"date": [">=", frappe.utils.nowdate()],
"status": "Upcoming",
},
[ [
"name", "name",
"date", "date",