Skip to content

Commit

Permalink
Merge pull request #1670 from vinyselopal/appraisal-enhancement
Browse files Browse the repository at this point in the history
feat: Formula-based Final Score calculation in Appraisals
  • Loading branch information
ruchamahabal committed May 29, 2024
2 parents 10fdc7d + 70e30c7 commit adb4c57
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 18 deletions.
23 changes: 22 additions & 1 deletion hrms/hr/doctype/appraisal/appraisal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from hrms.hr.doctype.appraisal_cycle.appraisal_cycle import validate_active_appraisal_cycle
from hrms.hr.utils import validate_active_employee
from hrms.payroll.utils import sanitize_expression


class Appraisal(Document):
Expand Down Expand Up @@ -179,7 +180,27 @@ def calculate_avg_feedback_score(self, update=False):
self.db_update()

def calculate_final_score(self):
final_score = (flt(self.total_score) + flt(self.avg_feedback_score) + flt(self.self_score)) / 3
final_score = 0
appraisal_cycle_doc = frappe.get_cached_doc("Appraisal Cycle", self.appraisal_cycle)

formula = appraisal_cycle_doc.final_score_formula
based_on_formula = appraisal_cycle_doc.calculate_final_score_based_on_formula

if based_on_formula:
employee_doc = frappe.get_cached_doc("Employee", self.employee)
data = {
"goal_score": flt(self.total_score),
"average_feedback_score": flt(self.avg_feedback_score),
"self_appraisal_score": flt(self.self_score),
}
data.update(appraisal_cycle_doc.as_dict())
data.update(employee_doc.as_dict())
data.update(self.as_dict())

sanitized_formula = sanitize_expression(formula)
final_score = frappe.safe_eval(sanitized_formula, data)
else:
final_score = (flt(self.total_score) + flt(self.avg_feedback_score) + flt(self.self_score)) / 3

self.final_score = flt(final_score, self.precision("final_score"))

Expand Down
22 changes: 21 additions & 1 deletion hrms/hr/doctype/appraisal/test_appraisal.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,26 @@ def test_manual_kra_rating(self):
def test_final_score(self):
cycle = create_appraisal_cycle(designation="Engineer", kra_evaluation_method="Manual Rating")
cycle.create_appraisals()
appraisal = self.setup_appraisal(cycle)

self.assertEqual(appraisal.final_score, 3.767)

def test_final_score_using_formula(self):
cycle = create_appraisal_cycle(designation="Engineer", kra_evaluation_method="Manual Rating")
cycle.update(
{
"calculate_final_score_based_on_formula": 1,
"final_score_formula": "(goal_score + self_appraisal_score + average_feedback_score)/3 if self_appraisal_score else (goal_score + self_appraisal_score)/2",
}
)
cycle.save()
cycle.create_appraisals()

appraisal = self.setup_appraisal(cycle)

self.assertEqual(appraisal.final_score, 3.767)

def setup_appraisal(self, cycle):
appraisal = frappe.db.exists("Appraisal", {"appraisal_cycle": cycle.name, "employee": self.employee1})
appraisal = frappe.get_doc("Appraisal", appraisal)

Expand Down Expand Up @@ -97,7 +116,8 @@ def test_final_score(self):
feedback.submit()

appraisal.reload()
self.assertEqual(appraisal.final_score, 3.767)

return appraisal

def test_goal_score(self):
"""
Expand Down
81 changes: 66 additions & 15 deletions hrms/hr/doctype/appraisal_cycle/appraisal_cycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ frappe.ui.form.on("Appraisal Cycle", {

frm.trigger("show_custom_buttons");
frm.trigger("show_appraisal_summary");
frm.trigger("set_autocompletions_for_final_score_formula");
},

show_custom_buttons(frm) {
Expand All @@ -26,28 +27,42 @@ frappe.ui.form.on("Appraisal Cycle", {
frappe.set_route("Tree", "Goal");
});

let className = "";
let appraisals_created = frm.doc.__onload?.appraisals_created;

if (frm.doc.status !== "Completed") {
className = appraisals_created ? "btn-default" : "btn-primary";

frm.add_custom_button(__("Create Appraisals"), () => {
frm.trigger("create_appraisals");
}).addClass(className);
if (appraisals_created) {
frm.add_custom_button(__("Create Appraisals"), () => {
frm.trigger("create_appraisals");
});
} else {
frm.page.set_primary_action(__("Create Appraisals"), () => {
frm.trigger("create_appraisals");
});
}
}

className = appraisals_created ? "btn-primary" : "btn-default";

if (frm.doc.status === "Not Started") {
frm.add_custom_button(__("Start"), () => {
frm.set_value("status", "In Progress");
frm.save();
}).addClass(className);
if (appraisals_created) {
frm.page.set_primary_action(__("Start"), () => {
frm.set_value("status", "In Progress");
frm.save();
});
} else {
frm.add_custom_button(__("Start"), () => {
frm.set_value("status", "In Progress");
frm.save();
});
}
} else if (frm.doc.status === "In Progress") {
frm.add_custom_button(__("Mark as Completed"), () => {
frm.trigger("complete_cycle");
}).addClass(className);
if (appraisals_created) {
frm.page.set_primary_action(__("Mark as Completed"), () => {
frm.trigger("complete_cycle");
});
} else {
frm.add_custom_button(__("Mark as Completed"), () => {
frm.trigger("complete_cycle");
});
}
} else if (frm.doc.status === "Completed") {
frm.add_custom_button(__("Mark as In Progress"), () => {
frm.set_value("status", "In Progress");
Expand All @@ -56,6 +71,42 @@ frappe.ui.form.on("Appraisal Cycle", {
}
},

set_autocompletions_for_final_score_formula: async (frm) => {
const autocompletions = [
{
value: "goal_score",
score: 10,
meta: __("Total Goal Score"),
},
{
value: "average_feedback_score",
score: 10,
meta: __("Average Feedback Score"),
},
{
value: "self_appraisal_score",
score: 10,
meta: __("Self Appraisal Score"),
},
];

await Promise.all(
["Employee", "Appraisal Cycle", "Appraisal"].map((doctype) =>
frappe.model.with_doctype(doctype, () => {
autocompletions.push(
...frappe.get_meta(doctype).fields.map((f) => ({
value: f.fieldname,
score: 8,
meta: __("{0} Field", [doctype]),
})),
);
}),
),
);

frm.set_df_property("final_score_formula", "autocompletions", autocompletions);
},

get_employees(frm) {
frappe.call({
method: "set_employees",
Expand Down
31 changes: 30 additions & 1 deletion hrms/hr/doctype/appraisal_cycle/appraisal_cycle.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"section_break_4",
"description",
"settings_section",
"column_break_vhzx",
"kra_evaluation_method",
"section_break_zykh",
"calculate_final_score_based_on_formula",
"final_score_formula",
"applicable_for_tab",
"filters_section",
"branch",
Expand Down Expand Up @@ -154,6 +158,31 @@
"label": "Status",
"options": "Not Started\nIn Progress\nCompleted",
"read_only": 1
},
{
"depends_on": "calculate_final_score_based_on_formula",
"fieldname": "final_score_formula",
"fieldtype": "Code",
"label": "Final Score Formula",
"mandatory_depends_on": "calculate_final_score_based_on_formula",
"max_height": "5rem",
"options": "PythonExpression"
},
{
"default": "0",
"description": "By default, the Final Score is calculated as the average of Goal Score, Feedback Score, and Self Appraisal Score. Enable this to set a different formula",
"fieldname": "calculate_final_score_based_on_formula",
"fieldtype": "Check",
"label": "Calculate Final Score based on Formula"
},
{
"fieldname": "column_break_vhzx",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_zykh",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"index_web_pages_for_search": 1,
Expand All @@ -171,7 +200,7 @@
"link_fieldname": "appraisal_cycle"
}
],
"modified": "2024-03-27 13:06:31.540312",
"modified": "2024-05-29 18:15:06.443594",
"modified_by": "Administrator",
"module": "HR",
"name": "Appraisal Cycle",
Expand Down

0 comments on commit adb4c57

Please sign in to comment.