fix: certificate request flow and other minor changes

This commit is contained in:
Jannat Patel
2022-04-11 15:35:40 +05:30
parent cb0087c433
commit 70e1e550e3
9 changed files with 193 additions and 107 deletions

View File

@@ -33,6 +33,18 @@ class CourseEvaluator(Document):
frappe.throw(_("Slot Times are overlapping for some schedules."))
@frappe.whitelist()
def get_schedule(course):
def get_schedule(course, date):
evaluator = frappe.db.get_value("LMS Course", course, "evaluator")
return frappe.get_all("Evaluator Schedule", filters={"parent": evaluator}, fields=["day", "start_time", "end_time"])
all_slots = frappe.get_all("Evaluator Schedule",
filters = { "parent": evaluator },
fields = ["day", "start_time", "end_time"])
booked_slots = frappe.get_all("LMS Certificate Request",
filters = {"course": course, "date": date},
fields = ["start_time"])
for slot in booked_slots:
same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots))
if len(same_slot):
all_slots.remove(same_slot[0])
return all_slots

View File

@@ -191,6 +191,7 @@
"fieldname": "evaluator",
"fieldtype": "Link",
"label": "Evaluator",
"mandatory_depends_on": "eval: doc.grant_certificate_after == \"Evaluation\"",
"options": "Course Evaluator"
},
{
@@ -236,7 +237,7 @@
"link_fieldname": "course"
}
],
"modified": "2022-04-07 12:27:05.353788",
"modified": "2022-04-08 14:36:22.254656",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Course",

View File

@@ -1410,3 +1410,7 @@ pre {
.course-content-parent .course-details-outline .course-home-headings {
display: none;
}
.btn-outline-primary {
border: 1px solid var(--primary-color);
}

View File

@@ -34,11 +34,11 @@
{% macro BreadCrumb(course, lesson) %}
<div class="breadcrumb">
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ lesson.title }}</span>
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
<span class="breadcrumb-destination">{{ lesson.title }}</span>
</div>
{% endmacro %}
@@ -94,7 +94,7 @@
{{ render_html(lesson.body) }}
{% else %}
<div class="">
<a class="button is-primary pull-right" href="/courses/{{ course.name }}"> {{ _("Start Learning") }} </a>
<div class="button is-primary pull-right join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
<div class=""> {{ _("This lesson is not available for preview. Please join the course to access it.") }} </div>
</div>
{% endif %}

View File

@@ -1,49 +1,53 @@
frappe.ready(() => {
localStorage.removeItem($("#quiz-title").text());
fetch_assignments();
localStorage.removeItem($("#quiz-title").text());
fetch_assignments();
save_current_lesson();
save_current_lesson();
$(".option").click((e) => {
enable_check(e);
})
$(".option").click((e) => {
enable_check(e);
})
$(".mark-progress").click((e) => {
mark_progress(e);
});
$(".mark-progress").click((e) => {
mark_progress(e);
});
$(".next").click((e) => {
mark_progress(e);
});
$(".next").click((e) => {
mark_progress(e);
});
$("#summary").click((e) => {
quiz_summary(e);
});
$("#summary").click((e) => {
quiz_summary(e);
});
$("#check").click((e) => {
check_answer(e);
});
$("#check").click((e) => {
check_answer(e);
});
$("#next").click((e) => {
mark_active_question(e);
});
$("#next").click((e) => {
mark_active_question(e);
});
$("#try-again").click((e) => {
try_quiz_again(e);
});
$("#try-again").click((e) => {
try_quiz_again(e);
});
$("#certification").click((e) => {
create_certificate(e);
});
$("#certification").click((e) => {
create_certificate(e);
});
$(".submit-work").click((e) => {
attach_work(e);
});
$(".submit-work").click((e) => {
attach_work(e);
});
$(".clear-work").click((e) => {
clear_work(e);
});
$(".clear-work").click((e) => {
clear_work(e);
});
$(".join-batch").click((e) => {
join_course(e)
});
});
@@ -240,6 +244,36 @@ const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
};
const join_course = (e) => {
e.preventDefault();
let course = $(e.currentTarget).attr("data-course")
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
let batch = $(e.currentTarget).attr("data-batch");
batch = batch ? decodeURIComponent(batch) : "";
frappe.call({
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch ? batch : "",
"course": course
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint({
"title": __("Successfully Enrolled"),
"message": __("You are now a student of this course.")
});
setTimeout(function () {
window.location.href = `/courses/${course}/learn/1.1`;
}, 2000);
}
}
});
};
const create_certificate = (e) => {
e.preventDefault();
course = $(".title").attr("data-course");

View File

@@ -105,7 +105,7 @@
{{ _("You have opted to be notified for this course. You will receive an email when the course becomes available.") }}
</div>
{% if certificate_request %}
{% if certificate_request and not certificate %}
<p class="mb-2"> <b>{{ _("Evaluation On: ") }}</b>
{{ _("{0} at {1}").format(frappe.utils.format_date(certificate_request.date, "medium"),
frappe.utils.format_time(certificate_request.start_time, "short")) }} </p>
@@ -199,7 +199,6 @@
</a>
{% endif %}
{% set certificate = is_certified(course.name) %}
{% set progress = frappe.utils.cint(membership.progress) %}
{% if membership and course.enable_certification %}
{% if certificate %}
@@ -225,7 +224,7 @@
<div class="modal-content">
<div class="modal-header">
<div class="font-weight-bold">{{ _("Pick a Slot") }}</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<button type="button" class="close close-slot-modal" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
@@ -238,8 +237,8 @@
</div>
<div class="control-input-wrapper">
<div class="control-input">
<input type="date" class="input-with-feedback form-control bold" data-fieldtype="Date"
id="slot-date" min="{{ frappe.utils.format_date(frappe.utils.getdate(), 'yyyy-mm-dd') }}">
<input type="date" class="input-with-feedback form-control bold" data-fieldtype="Date" data-course="{{ course.name | urlencode }}"
id="slot-date" min="{{ frappe.utils.format_date(frappe.utils.add_days(frappe.utils.getdate(), 1), 'yyyy-mm-dd') }}">
</div>
</div>
</div>
@@ -256,6 +255,10 @@
<p id="no-slots-message" class="small text-danger hide"> {{ _("There are no slots available on this day.") }} </p>
</form>
</div>
<div class="modal-footer">
<div class="button is-primary" data-course="{{ course.name | urlencode}}" id="submit-slot">
{{ _("Submit") }}</div>
</div>
</div>
</div>
</div>

View File

@@ -7,7 +7,7 @@ frappe.ready(() => {
});
$(".join-batch").click((e) => {
join_course(e)
join_course(e);
});
$(".view-all-mentors").click((e) => {
@@ -46,10 +46,18 @@ frappe.ready(() => {
display_slots(e);
});
$(document).on("click", ".slot", (e) => {
$("#submit-slot").click((e) => {
submit_slot(e);
});
$(".close-slot-modal").click((e) => {
close_slot_modal(e);
});
$(document).on("click", ".slot", (e) => {
select_slot(e);
});
$(document).scroll(function() {
let timer;
clearTimeout(timer);
@@ -93,31 +101,35 @@ var cancel_mentor_request = (e) => {
})
}
var join_course = (e) => {
e.preventDefault();
var course = $(e.currentTarget).attr("data-course")
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
var batch = $(e.currentTarget).attr("data-batch");
batch = batch ? decodeURIComponent(batch) : "";
frappe.call({
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch ? batch : "",
"course": course
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint(__("You are now a student of this course."));
setTimeout(function () {
window.location.href = `/courses/${course}/learn/1.1`;
}, 2000);
}
const join_course = (e) => {
e.preventDefault();
let course = $(e.currentTarget).attr("data-course");
if (frappe.session.user == "Guest") {
window.location.href = `/login?redirect-to=/courses/${course}`;
return;
}
})
}
let batch = $(e.currentTarget).attr("data-batch");
batch = batch ? decodeURIComponent(batch) : "";
frappe.call({
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
"args": {
"batch": batch ? batch : "",
"course": course
},
"callback": (data) => {
if (data.message == "OK") {
frappe.msgprint({
"title": __("Successfully Enrolled"),
"message": __("You are now a student of this course.")
});
setTimeout(function () {
window.location.href = `/courses/${course}/learn/1.1`;
}, 2000);
}
}
})
};
var view_all_mentors = (e) => {
$(".wrapped").each((i, element) => {
@@ -251,29 +263,14 @@ const submit_for_review = (e) => {
};
const apply_cetificate = (e) => {
frappe.call({
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
args: {
"course": $(e.currentTarget).data("course")
},
callback: (data) => {
let options = "";
data.message.forEach((obj) => {
options += `<button type="button" class="btn btn-sm btn-secondary mr-3 slot hide"
data-course="${$(e.currentTarget).data("course")}"
data-day="${obj.day}" data-start="${obj.start_time}" data-end="${obj.end_time}">
${obj.day} ${obj.start_time} - ${obj.end_time}</button>`;
});
e.preventDefault();
$("#slot-modal .slots").html(options);
$("#slot-modal").modal("show");
}
})
$("#slot-modal").modal("show");
};
const submit_slot = (e) => {
e.preventDefault();
const slot = $(e.currentTarget);
const slot = window.selected_slot;
frappe.call({
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request",
args: {
@@ -294,17 +291,51 @@ const submit_slot = (e) => {
};
const display_slots = (e) => {
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
const day = weekday[new Date($(e.currentTarget).val()).getDay()]
frappe.call({
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
args: {
"course": $(e.currentTarget).data("course"),
"date": $(e.currentTarget).val()
},
callback: (data) => {
let options = "";
data.message.forEach((obj) => {
options += `<button type="button" class="btn btn-sm btn-secondary mr-3 slot hide"
data-course="${$(e.currentTarget).data("course")}"
data-day="${obj.day}" data-start="${obj.start_time}" data-end="${obj.end_time}">
${format_time(obj.start_time)} - ${format_time(obj.end_time)}</button>`;
});
e.preventDefault();
$("#slot-modal .slots").html(options);
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
const day = weekday[new Date($(e.currentTarget).val()).getDay()]
$(".slot").addClass("hide");
$(".slot-label").addClass("hide");
$(".slot").addClass("hide");
$(".slot-label").addClass("hide");
if ($(`[data-day='${day}']`).length) {
$(".slot-label").removeClass("hide");
$(`[data-day='${day}']`).removeClass("hide");
$("#no-slots-message").addClass("hide");
} else {
$("#no-slots-message").removeClass("hide");
}
if ($(`[data-day='${day}']`).length) {
$(".slot-label").removeClass("hide");
$(`[data-day='${day}']`).removeClass("hide");
$("#no-slots-message").addClass("hide");
} else {
$("#no-slots-message").removeClass("hide");
}
}
});
};
const select_slot = (e) => {
$(".slot").removeClass("btn-outline-primary");
$(e.currentTarget).addClass("btn-outline-primary");
window.selected_slot = $(e.currentTarget);
};
const format_time = (time) => {
let date = moment(new Date()).format("ddd MMM DD YYYY");
return moment(`${date} ${time}`).format("HH:mm a");
};
const close_slot_modal = (e) => {
$("#slot-date").val("");
$(".slot-label").addClass("hide");
}

View File

@@ -1,6 +1,6 @@
import frappe
from lms.lms.doctype.lms_settings.lms_settings import check_profile_restriction
from lms.lms.utils import get_membership, is_instructor
from lms.lms.utils import get_membership, is_instructor, is_certified
def get_context(context):
context.no_cache = 1
@@ -32,6 +32,7 @@ def get_context(context):
context.membership = membership
context.restriction = check_profile_restriction()
context.show_start_learing_cta = show_start_learing_cta(course, membership, context.restriction)
context.certificate = is_certified(course.name)
context.certificate_request = frappe.db.get_value("LMS Certificate Request",
{
"course": course.name,

View File

@@ -14,9 +14,9 @@ def get_context(context):
def get_courses():
courses = frappe.get_all("LMS Course",
filters={"published": True},
fields=["name", "upcoming", "title", "image", "enable_certification",
"paid_certificate", "price_certificate", "currency"])
filters={"published": True},
fields=["name", "upcoming", "title", "image", "enable_certification",
"paid_certificate", "price_certificate", "currency"])
live_courses, upcoming_courses = [], []
for course in courses: