feat: evaluator unavailability
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
<div class="mb-1.5 text-sm text-gray-600">
|
||||
{{ __('Date') }}
|
||||
</div>
|
||||
<DatePicker v-model="evaluation.date" />
|
||||
<FormControl type="date" v-model="evaluation.date" />
|
||||
</div>
|
||||
<div v-if="slots.data?.length">
|
||||
<div class="mb-1.5 text-sm text-gray-600">
|
||||
@@ -57,7 +57,7 @@
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Dialog, createResource, Select, DatePicker } from 'frappe-ui'
|
||||
import { Dialog, createResource, Select, FormControl } from 'frappe-ui'
|
||||
import { defineModel, reactive, watch, inject } from 'vue'
|
||||
import { createToast, formatTime } from '@/utils/'
|
||||
|
||||
@@ -168,7 +168,7 @@ watch(
|
||||
() => evaluation.date,
|
||||
(date) => {
|
||||
evaluation.start_time = ''
|
||||
if (date) {
|
||||
if (date && evaluation.course) {
|
||||
slots.submit(evaluation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ const profile = createResource({
|
||||
|
||||
const setActiveTab = () => {
|
||||
let fragments = route.path.split('/')
|
||||
let sections = ['certificates', 'settings', 'evaluator']
|
||||
let sections = ['certificates', 'roles', 'evaluations']
|
||||
sections.forEach((section) => {
|
||||
if (fragments.includes(section)) {
|
||||
activeTab.value = convertToTitleCase(section)
|
||||
@@ -130,8 +130,8 @@ watchEffect(() => {
|
||||
let route = {
|
||||
About: { name: 'ProfileAbout' },
|
||||
Certificates: { name: 'ProfileCertificates' },
|
||||
Settings: { name: 'ProfileSettings' },
|
||||
Evaluato: { name: 'ProfileEvaluator' },
|
||||
Roles: { name: 'ProfileRoles' },
|
||||
Evaluations: { name: 'ProfileEvaluator' },
|
||||
}[activeTab.value]
|
||||
router.push(route)
|
||||
}
|
||||
@@ -147,9 +147,9 @@ const isSessionUser = () => {
|
||||
|
||||
const getTabButtons = () => {
|
||||
let buttons = [{ label: 'About' }, { label: 'Certificates' }]
|
||||
if ($user.data?.is_moderator) buttons.push({ label: 'Settings' })
|
||||
if ($user.data?.is_moderator) buttons.push({ label: 'Roles' })
|
||||
if (isSessionUser() && $user.data?.is_evaluator)
|
||||
buttons.push({ label: 'Evaluation Slots' })
|
||||
buttons.push({ label: 'Evaluations' })
|
||||
|
||||
return buttons
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
:key="certificate.name"
|
||||
class="bg-white shadow rounded-lg p-3 cursor-pointer"
|
||||
>
|
||||
<div class="font-medium">
|
||||
<div class="font-medium leading-5">
|
||||
{{ certificate.course_title }}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
|
||||
@@ -1,2 +1,278 @@
|
||||
<template>Evaluator</template>
|
||||
<script setup></script>
|
||||
<template>
|
||||
<div class="mt-7 mb-20">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900">
|
||||
{{ __('My availability') }}
|
||||
</h2>
|
||||
|
||||
<div class="w-3/4">
|
||||
<div class="grid grid-cols-4 gap-4 text-sm text-gray-700 mb-4">
|
||||
<div>
|
||||
{{ __('Day') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ __('Start Time') }}
|
||||
</div>
|
||||
<div>
|
||||
{{ __('End Time') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="slots.data"
|
||||
v-for="slot in slots.data.schedule"
|
||||
class="grid grid-cols-4 gap-4 mb-4 group"
|
||||
>
|
||||
<FormControl
|
||||
type="select"
|
||||
:options="days"
|
||||
v-model="slot.day"
|
||||
@focusout.stop="update(slot.name, 'day', slot.day)"
|
||||
/>
|
||||
<FormControl
|
||||
type="time"
|
||||
v-model="slot.start_time"
|
||||
@focusout.stop="update(slot.name, 'start_time', slot.start_time)"
|
||||
/>
|
||||
<FormControl
|
||||
type="time"
|
||||
v-model="slot.end_time"
|
||||
@focusout.stop="update(slot.name, 'end_time', slot.end_time)"
|
||||
/>
|
||||
<X
|
||||
@click="deleteRow(slot.name)"
|
||||
class="w-6 h-auto stroke-1.5 text-red-900 rounded-md cursor-pointer p-1 bg-red-100 hidden group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 gap-4 mb-4" v-show="showSlotsTemplate">
|
||||
<FormControl
|
||||
type="select"
|
||||
:options="days"
|
||||
v-model="newSlot.day"
|
||||
@focusout.stop="add()"
|
||||
/>
|
||||
<FormControl
|
||||
type="time"
|
||||
v-model="newSlot.start_time"
|
||||
@focusout.stop="add()"
|
||||
/>
|
||||
<FormControl
|
||||
type="time"
|
||||
v-model="newSlot.end_time"
|
||||
@focusout.stop="add()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button @click="showSlotsTemplate = 1">
|
||||
<template #prefix>
|
||||
<Plus class="w-4 h-4 stroke-1.5 text-gray-700" />
|
||||
</template>
|
||||
{{ __('Add Slot') }}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mt-10 w-3/4">
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-900">
|
||||
{{ __('I am unavailable') }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<FormControl
|
||||
type="date"
|
||||
:label="__('From')"
|
||||
v-model="from"
|
||||
@change.stop="
|
||||
() => {
|
||||
updateUnavailability.submit({
|
||||
field: 'unavailable_from',
|
||||
value: from,
|
||||
})
|
||||
}
|
||||
"
|
||||
/>
|
||||
<FormControl
|
||||
type="date"
|
||||
:label="__('To')"
|
||||
v-model="to"
|
||||
@change.stop="
|
||||
() => {
|
||||
updateUnavailability.submit({
|
||||
field: 'unavailable_to',
|
||||
value: to,
|
||||
})
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { createResource, FormControl, Button } from 'frappe-ui'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { showToast, convertToTitleCase } from '@/utils'
|
||||
import { Plus, X } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps({
|
||||
profile: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const showSlotsTemplate = ref(0)
|
||||
const from = ref(null)
|
||||
const to = ref(null)
|
||||
|
||||
const newSlot = reactive({
|
||||
day: '',
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
})
|
||||
|
||||
const slots = createResource({
|
||||
url: 'lms.lms.api.get_evaluator_details',
|
||||
params: {
|
||||
evaluator: props.profile.data?.name,
|
||||
},
|
||||
auto: true,
|
||||
})
|
||||
|
||||
const createSlot = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'Evaluator Schedule',
|
||||
parent: slots.data?.name,
|
||||
parentfield: 'schedule',
|
||||
parenttype: 'Course Evaluator',
|
||||
...newSlot,
|
||||
},
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Slot added successfully', 'check')
|
||||
slots.reload()
|
||||
showSlotsTemplate.value = 0
|
||||
newSlot.day = ''
|
||||
newSlot.start_time = ''
|
||||
newSlot.end_time = ''
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.messages?.[0] || err, 'x')
|
||||
},
|
||||
})
|
||||
|
||||
const updateSlot = createResource({
|
||||
url: 'frappe.client.set_value',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Evaluator Schedule',
|
||||
name: values.name,
|
||||
fieldname: values.field,
|
||||
value: values.value,
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Availability updated successfully', 'check')
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.messages?.[0] || err, 'x')
|
||||
},
|
||||
})
|
||||
|
||||
const deleteSlot = createResource({
|
||||
url: 'frappe.client.delete',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Evaluator Schedule',
|
||||
name: values.name,
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Slot deleted successfully', 'check')
|
||||
slots.reload()
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.messages?.[0] || err, 'x')
|
||||
},
|
||||
})
|
||||
|
||||
const updateUnavailability = createResource({
|
||||
url: 'frappe.client.set_value',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doctype: 'Course Evaluator',
|
||||
name: slots.data?.name,
|
||||
fieldname: values.field,
|
||||
value: values.value,
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
showToast('Success', 'Unavailability updated successfully', 'check')
|
||||
},
|
||||
onError(err) {
|
||||
showToast('Error', err.messages?.[0] || err, 'x')
|
||||
},
|
||||
})
|
||||
|
||||
const update = (name, field, value) => {
|
||||
updateSlot.submit(
|
||||
{
|
||||
name,
|
||||
field,
|
||||
value,
|
||||
},
|
||||
{
|
||||
validate() {
|
||||
if (!value) {
|
||||
return `Please enter a value for ${convertToTitleCase(field)}`
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const add = () => {
|
||||
if (!newSlot.day || !newSlot.start_time || !newSlot.end_time) {
|
||||
return
|
||||
}
|
||||
createSlot.submit()
|
||||
}
|
||||
|
||||
const deleteRow = (name) => {
|
||||
deleteSlot.submit({ name })
|
||||
}
|
||||
|
||||
const days = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: 'Monday',
|
||||
value: 'Monday',
|
||||
},
|
||||
{
|
||||
label: 'Tuesday',
|
||||
value: 'Tuesday',
|
||||
},
|
||||
{
|
||||
label: 'Wednesday',
|
||||
value: 'Wednesday',
|
||||
},
|
||||
{
|
||||
label: 'Thursday',
|
||||
value: 'Thursday',
|
||||
},
|
||||
{
|
||||
label: 'Friday',
|
||||
value: 'Friday',
|
||||
},
|
||||
{
|
||||
label: 'Saturday',
|
||||
value: 'Saturday',
|
||||
},
|
||||
{
|
||||
label: 'Sunday',
|
||||
value: 'Sunday',
|
||||
},
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -73,13 +73,13 @@ const routes = [
|
||||
component: () => import('@/pages/ProfileCertificates.vue'),
|
||||
},
|
||||
{
|
||||
name: 'ProfileSettings',
|
||||
path: 'settings',
|
||||
component: () => import('@/pages/ProfileSettings.vue'),
|
||||
name: 'ProfileRoles',
|
||||
path: 'roles',
|
||||
component: () => import('@/pages/ProfileRoles.vue'),
|
||||
},
|
||||
{
|
||||
name: 'ProfileEvaluator',
|
||||
path: 'evaluator',
|
||||
path: 'evaluations',
|
||||
component: () => import('@/pages/ProfileEvaluator.vue'),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -298,3 +298,16 @@ def get_unsplash_photos(keyword=None):
|
||||
return get_by_keyword(keyword)
|
||||
|
||||
return frappe.cache().get_value("unsplash_photos", generator=get_list)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_evaluator_details(evaluator):
|
||||
frappe.only_for("Batch Evaluator")
|
||||
|
||||
if frappe.db.exists("Course Evaluator", {"evaluator": evaluator}):
|
||||
return frappe.get_doc("Course Evaluator", evaluator, as_dict=1)
|
||||
else:
|
||||
doc = frappe.new_doc("Course Evaluator")
|
||||
doc.evaluator = evaluator
|
||||
doc.insert()
|
||||
return doc.as_dict()
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"evaluator",
|
||||
"schedule"
|
||||
"schedule",
|
||||
"unavailability_section",
|
||||
"unavailable_from",
|
||||
"column_break_ahzi",
|
||||
"unavailable_to"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -23,11 +27,30 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Schedule",
|
||||
"options": "Evaluator Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "unavailability_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Unavailability"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ahzi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "unavailable_from",
|
||||
"fieldtype": "Date",
|
||||
"label": "From"
|
||||
},
|
||||
{
|
||||
"fieldname": "unavailable_to",
|
||||
"fieldtype": "Date",
|
||||
"label": "To"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-15 11:21:52.182338",
|
||||
"modified": "2024-04-15 18:45:08.614466",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Course Evaluator",
|
||||
|
||||
@@ -6,6 +6,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from lms.lms.utils import get_evaluator
|
||||
from datetime import datetime
|
||||
from frappe.utils import get_time
|
||||
|
||||
|
||||
class CourseEvaluator(Document):
|
||||
@@ -14,7 +15,7 @@ class CourseEvaluator(Document):
|
||||
|
||||
def validate_time_slots(self):
|
||||
for schedule in self.schedule:
|
||||
if schedule.start_time >= schedule.end_time:
|
||||
if get_time(schedule.start_time) >= get_time(schedule.end_time):
|
||||
frappe.throw(_("Start Time cannot be greater than End Time"))
|
||||
|
||||
self.validate_overlaps(schedule)
|
||||
@@ -26,11 +27,21 @@ class CourseEvaluator(Document):
|
||||
overlap = False
|
||||
|
||||
for slot in same_day_slots:
|
||||
if schedule.start_time <= slot.start_time < schedule.end_time:
|
||||
if (
|
||||
get_time(schedule.start_time)
|
||||
<= get_time(slot.start_time)
|
||||
< get_time(schedule.end_time)
|
||||
):
|
||||
overlap = True
|
||||
if schedule.start_time < slot.end_time <= schedule.end_time:
|
||||
if (
|
||||
get_time(schedule.start_time)
|
||||
< get_time(slot.end_time)
|
||||
<= get_time(schedule.end_time)
|
||||
):
|
||||
overlap = True
|
||||
if slot.start_time < schedule.start_time and schedule.end_time < slot.end_time:
|
||||
if get_time(slot.start_time) < get_time(schedule.start_time) and get_time(
|
||||
schedule.end_time
|
||||
) < get_time(slot.end_time):
|
||||
overlap = True
|
||||
|
||||
if overlap:
|
||||
|
||||
@@ -39,8 +39,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "course.evaluator",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "evaluator",
|
||||
"fieldtype": "Link",
|
||||
"label": "Evaluator",
|
||||
@@ -109,7 +107,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-15 11:23:03.933035",
|
||||
"modified": "2024-04-16 11:01:28.336807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Certificate Request",
|
||||
|
||||
@@ -11,10 +11,35 @@ from lms.lms.utils import get_evaluator
|
||||
|
||||
class LMSCertificateRequest(Document):
|
||||
def validate(self):
|
||||
self.set_evaluator()
|
||||
self.validate_unavailability()
|
||||
self.validate_slot()
|
||||
self.validate_if_existing_requests()
|
||||
self.validate_evaluation_end_date()
|
||||
|
||||
def set_evaluator(self):
|
||||
if not self.evaluator:
|
||||
self.evaluator = get_evaluator(self.course, self.batch_name)
|
||||
|
||||
def validate_unavailability(self):
|
||||
unavailable = frappe.db.get_value(
|
||||
"Course Evaluator", self.evaluator, ["unavailable_from", "unavailable_to"], as_dict=1
|
||||
)
|
||||
if (
|
||||
unavailable.unavailable_from
|
||||
and unavailable.unavailable_to
|
||||
and getdate(self.date) >= unavailable.unavailable_from
|
||||
and getdate(self.date) <= unavailable.unavailable_to
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Evaluator is unavailable from {0} to {1}. Please select a date after {1}"
|
||||
).format(
|
||||
format_date(unavailable.unavailable_from, "medium"),
|
||||
format_date(unavailable.unavailable_to, "medium"),
|
||||
)
|
||||
)
|
||||
|
||||
def validate_slot(self):
|
||||
if frappe.db.exists(
|
||||
"LMS Certificate Request",
|
||||
|
||||
@@ -1798,6 +1798,6 @@ def get_roles(name):
|
||||
return {
|
||||
"moderator": has_course_moderator_role(name),
|
||||
"course_creator": has_course_instructor_role(name),
|
||||
"class_evaluator": has_course_evaluator_role(name),
|
||||
"batch_evaluator": has_course_evaluator_role(name),
|
||||
"lms_student": has_student_role(name),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user