Merge pull request #29506 from ruchamahabal/fix-interview-ratings

This commit is contained in:
Rucha Mahabal 2022-01-29 21:51:30 +05:30 committed by GitHub
commit cc4b4046ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 41 deletions

View File

@ -7,7 +7,7 @@ import datetime
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, get_link_to_form
from frappe.utils import cstr, flt, get_datetime, get_link_to_form
class DuplicateInterviewRoundError(frappe.ValidationError):
@ -18,6 +18,7 @@ class Interview(Document):
self.validate_duplicate_interview()
self.validate_designation()
self.validate_overlap()
self.set_average_rating()
def on_submit(self):
if self.status not in ['Cleared', 'Rejected']:
@ -67,6 +68,13 @@ class Interview(Document):
overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0]))
frappe.throw(overlapping_details, title=_('Overlap'))
def set_average_rating(self):
total_rating = 0
for entry in self.interview_details:
if entry.average_rating:
total_rating += entry.average_rating
self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0)
@frappe.whitelist()
def reschedule_interview(self, scheduled_on, from_time, to_time):

View File

@ -12,6 +12,7 @@ from frappe.utils import add_days, getdate, nowtime
from erpnext.hr.doctype.designation.test_designation import create_designation
from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError
from erpnext.hr.doctype.job_applicant.job_applicant import get_interview_details
from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant
@ -70,6 +71,20 @@ class TestInterview(unittest.TestCase):
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message)
def test_get_interview_details_for_applicant_dashboard(self):
job_applicant = create_job_applicant()
interview = create_interview_and_dependencies(job_applicant.name)
details = get_interview_details(job_applicant.name)
self.assertEqual(details.get('stars'), 5)
self.assertEqual(details.get('interviews').get(interview.name), {
'name': interview.name,
'interview_round': interview.interview_round,
'expected_average_rating': interview.expected_average_rating * 5,
'average_rating': interview.average_rating * 5,
'status': 'Pending'
})
def tearDown(self):
frappe.db.rollback()
@ -106,7 +121,8 @@ def create_interview_round(name, skill_set, interviewers=[], designation=None, s
interview_round = frappe.new_doc("Interview Round")
interview_round.round_name = name
interview_round.interview_type = create_interview_type()
interview_round.expected_average_rating = 4
# average rating = 4
interview_round.expected_average_rating = 0.8
if designation:
interview_round.designation = designation

View File

@ -57,7 +57,6 @@ class InterviewFeedback(Document):
def update_interview_details(self):
doc = frappe.get_doc('Interview', self.interview)
total_rating = 0
if self.docstatus == 2:
for entry in doc.interview_details:
@ -72,10 +71,6 @@ class InterviewFeedback(Document):
entry.comments = self.feedback
entry.result = self.result
if entry.average_rating:
total_rating += entry.average_rating
doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0)
doc.save()
doc.notify_update()

View File

@ -24,7 +24,7 @@ class TestInterviewFeedback(unittest.TestCase):
create_skill_set(['Leadership'])
interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings)
interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4})
interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8})
frappe.set_user(interviewer)
self.assertRaises(frappe.ValidationError, interview_feedback.save)
@ -50,7 +50,7 @@ class TestInterviewFeedback(unittest.TestCase):
avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0)
self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating)
self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2))
avg_on_interview_detail = frappe.db.get_value('Interview Detail', {
'parent': feedback_1.interview,
@ -59,7 +59,7 @@ class TestInterviewFeedback(unittest.TestCase):
}, 'average_rating')
# 1. average should be reflected in Interview Detail.
self.assertEqual(avg_on_interview_detail, feedback_1.average_rating)
self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2))
'''For Second Interviewer Feedback'''
interviewer = interview.interview_details[1].interviewer
@ -97,5 +97,5 @@ def get_skills_rating(interview_round):
skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"])
for d in skills:
d["rating"] = random.randint(1, 5)
d["rating"] = random.random()
return skills

View File

@ -21,9 +21,9 @@ frappe.ui.form.on("Job Applicant", {
create_custom_buttons: function(frm) {
if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") {
frm.add_custom_button(__("Create Interview"), function() {
frm.add_custom_button(__("Interview"), function() {
frm.events.create_dialog(frm);
});
}, __("Create"));
}
if (!frm.doc.__islocal) {
@ -40,10 +40,10 @@ frappe.ui.form.on("Job Applicant", {
frappe.route_options = {
"job_applicant": frm.doc.name,
"applicant_name": frm.doc.applicant_name,
"designation": frm.doc.job_opening,
"designation": frm.doc.job_opening || frm.doc.designation,
};
frappe.new_doc("Job Offer");
});
}, __("Create"));
}
}
},
@ -55,13 +55,16 @@ frappe.ui.form.on("Job Applicant", {
job_applicant: frm.doc.name
},
callback: function(r) {
$("div").remove(".form-dashboard-section.custom");
frm.dashboard.add_section(
frappe.render_template('job_applicant_dashboard', {
data: r.message
}),
__("Interview Summary")
);
if (r.message) {
$("div").remove(".form-dashboard-section.custom");
frm.dashboard.add_section(
frappe.render_template("job_applicant_dashboard", {
data: r.message.interviews,
number_of_stars: r.message.stars
}),
__("Interview Summary")
);
}
}
});
},

View File

@ -81,8 +81,13 @@ def get_interview_details(job_applicant):
fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"]
)
interview_detail_map = {}
meta = frappe.get_meta("Interview")
number_of_stars = meta.get_options("expected_average_rating") or 5
for detail in interview_details:
detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0
interview_detail_map[detail.name] = detail
return interview_detail_map
return {"interviews": interview_detail_map, "stars": number_of_stars}

View File

@ -17,24 +17,33 @@
<td class="text-left"> {%= key %} </td>
<td class="text-left"> {%= value["interview_round"] %} </td>
<td class="text-left"> {%= value["status"] %} </td>
<td class="text-left">
{% for (i = 0; i < value["expected_average_rating"]; i++) { %}
<span class="fa fa-star " style="color: #F6C35E;"></span>
{% } %}
{% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %}
<span class="fa fa-star " style="color: #E7E9EB;"></span>
{% } %}
</td>
<td class="text-left">
{% if(value["average_rating"]){ %}
{% for (i = 0; i < value["average_rating"]; i++) { %}
<span class="fa fa-star " style="color: #F6C35E;"></span>
{% } %}
{% for (i = 0; i < (5-value["average_rating"]); i++) { %}
<span class="fa fa-star " style="color: #E7E9EB;"></span>
{% } %}
{% } %}
</td>
{% let right_class = ''; %}
{% let left_class = ''; %}
{% $.each([value["expected_average_rating"], value["average_rating"]], (_, val) => { %}
<td class="text-left">
<div class="rating">
{% for (let i = 1; i <= number_of_stars; i++) { %}
{% if (i <= val) { %}
{% right_class = 'star-click'; %}
{% } else { %}
{% right_class = ''; %}
{% } %}
{% if ((i <= val) || ((i - 0.5) == val)) { %}
{% left_class = 'star-click'; %}
{% } else { %}
{% left_class = ''; %}
{% } %}
<svg class="icon icon-md" data-rating={{i}} viewBox="0 0 24 24" fill="none">
<path class="right-half {{ right_class }}" d="M11.9987 3.00011C12.177 3.00011 12.3554 3.09303 12.4471 3.27888L14.8213 8.09112C14.8941 8.23872 15.0349 8.34102 15.1978 8.3647L20.5069 9.13641C20.917 9.19602 21.0807 9.69992 20.7841 9.9892L16.9421 13.7354C16.8243 13.8503 16.7706 14.0157 16.7984 14.1779L17.7053 19.4674C17.7753 19.8759 17.3466 20.1874 16.9798 19.9945L12.2314 17.4973C12.1586 17.459 12.0786 17.4398 11.9987 17.4398V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
<path class="left-half {{ left_class }}" d="M11.9987 3.00011C11.8207 3.00011 11.6428 3.09261 11.5509 3.27762L9.15562 8.09836C9.08253 8.24546 8.94185 8.34728 8.77927 8.37075L3.42887 9.14298C3.01771 9.20233 2.85405 9.70811 3.1525 9.99707L7.01978 13.7414C7.13858 13.8564 7.19283 14.0228 7.16469 14.1857L6.25116 19.4762C6.18071 19.8842 6.6083 20.1961 6.97531 20.0045L11.7672 17.5022C11.8397 17.4643 11.9192 17.4454 11.9987 17.4454V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
</svg>
{% } %}
</div>
</td>
{% }); %}
</tr>
{% } %}
</tbody>