diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py index 4bb003ded18..a3b111ccb15 100644 --- a/erpnext/hr/doctype/interview/interview.py +++ b/erpnext/hr/doctype/interview/interview.py @@ -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): diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py index 1a2257a6d90..fdb11afe822 100644 --- a/erpnext/hr/doctype/interview/test_interview.py +++ b/erpnext/hr/doctype/interview/test_interview.py @@ -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 diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py index d046458f196..2ff00c1cac7 100644 --- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py @@ -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() diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py index d2ec5b9438e..19c464296ac 100644 --- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py +++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py @@ -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 diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index d7b1c6c9df3..c1e82571683 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -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") + ); + } } }); }, diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index 5b3d9bfb4ff..ccc21ced2cc 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -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} diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html index c286787a556..734b2fe5e8b 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html @@ -17,24 +17,33 @@