from odoo import api, fields, models, _ from odoo.exceptions import ValidationError RATING_VALUATION = { '1': 'unsatisfactory', '2': 'needs_improvements', '3': 'meet_expectation', '4': 'exceed_expectation', '5': 'outstanding', } class EmployeeAppraisal(models.Model): _name = 'employee.appraisal.template.config' _description = 'Employee Appraisal' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = "tracking_date desc" _rec_name = 'template_id' name = fields.Char(string="Reference", copy=False) employee_evaluator_name_id = fields.Many2one('employee.appraisal.evaluator', string="Employee Appraisal Evaluator") hr_apprai_id = fields.Many2one('hr.employee') hr_head_apprai_id = fields.Many2one('hr.employee') managerapp_id = fields.Many2one('hr.employee', string='Manager') # managerapp_id = fields.Many2one('hr.employee', compute='_compute_managerapp_id', string='Manager') performance_evaluator = fields.Selection([('manager', 'Manager'), ('colleague', 'Colleague'), ('own', 'Own')]) template_id = fields.Many2one('employee.appraisal.template', string="Template") stage_id = fields.Many2one('employee.stage.config', string='Stage') stage_name = fields.Char(related='stage_id.name', store=True) available_stage_ids = fields.Many2many('employee.stage.config', compute='_compute_available_stages') tracking_date = fields.Datetime(default=fields.Datetime.now, readonly=True, index=True) company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company) state = fields.Selection([ ('new', 'New'), # ('draft', 'Draft'), ('self_evaluation', 'Self Evaluation'), ('colleague_manager', 'colleague Feedback & Manager Evaluation'), # ('in_progress', 'Manager Evaluation'), ('hr_evaluation', 'HR Evaluation'), ('finance_team', 'Finance Evaluation'), ('management_team', 'Management Evaluation'), ('done', 'Completed'), ('reject', 'Rejected') ], string='Status', copy=False, tracking=1, default='new') appraisal_period_id = fields.Many2one('employee.appraisal.year') employee_appraisal_id = fields.Many2one('hr.employee') image_1920 = fields.Image(related='employee_appraisal_id.image_1920') employee_code = fields.Char(string='Employee Code', related='employee_appraisal_id.employee_id', store=True) department_appraisal_id = fields.Many2one("hr.department", string="Department", related="employee_appraisal_id.department_id", store=True) job_appraisal_id = fields.Many2one("hr.job", string="Job Position", related="employee_appraisal_id.job_id", store=True) kra_line_ids = fields.One2many('employee.appraisal.kra.line', 'config_id', string='Kra') manager_remarks = fields.Char(string="Manager Remarks") hr_remarks = fields.Char(string="HR Remarks") hr_head_remarks = fields.Text(string="HR Head Remarks") finance_head_remarks = fields.Text(string="Finance Head Remarks") colleague_feed_ids = fields.One2many('colleague.feedback', 'employee_appraisal_feed_id', 'Colleague Feed Back') created_by_id = fields.Many2one('hr.employee', string="Created By", default=lambda self: self.env.user.employee_id, readonly=True) created_user_id = fields.Many2one('res.users', default=lambda self: self.env.user, readonly=True) creator_email = fields.Char(related='created_by_id.work_email', string="Creator Email", readonly=True) notice_id = fields.Many2one('hr.notice.appraisal', string="Notice") start_date = fields.Date(string="Start Date") end_date = fields.Date(string="End Date") college_end_date = fields.Datetime(string="Colleges End Date") college_end_date_time = fields.Datetime(string="Colleague End Date") is_readonly = fields.Boolean(compute="_compute_is_readonly") mail_sent_employee_ids = fields.Many2many('hr.employee', string="Mail Sent Employees") seq = fields.Char(string="Sequence", readonly=True, copy=False) employee_ids = fields.Many2many('hr.employee', 'appraisal_config_employee_rel', 'config_id', 'employee_id', string="Employees") manager_ids = fields.Many2many('hr.employee', 'appraisal_config_manager_rel', 'config_id', 'manager_id', string="Managers") total_employee_score = fields.Float(string="Employee Total Points", compute="_compute_total_scores", store=True) total_manager_score = fields.Float(string="Manager Total Points", compute="_compute_total_scores", store=True) total_hr_score = fields.Float(string="HR Total Points", compute="_compute_total_scores", store=True) manager_email = fields.Char(compute="_compute_manager_email", string="Manager Email") Note_appraisal = fields.Char(string="User Note", default="Please click KRA's Name, To open the KPI's", Readonly=True) overall_score = fields.Float(compute="_compute_overall_scores", store=True) overall_rating = fields.Selection([ ('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ], string="Overall Rating") overall_rating_value = fields.Selection([ ('unsatisfactory', 'Unsatisfactory'), ('needs_improvements', 'Needs Improvements'), ('meet_expectation', 'Meets Expectation'), ('exceed_expectation', 'Exceeds Expectation'), ('outstanding', 'Outstanding'), ], string="Overall Rating Value") overall_rating_star = fields.Selection([ ('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ], string="Overall Stars") employee_overall_score = fields.Float(compute='_compute_overall_scores', store=True) manager_overall_score = fields.Float(compute='_compute_overall_scores', store=True) hr_overall_score = fields.Float(compute='_compute_overall_scores', store=True) employee_overall_star = fields.Selection([ ('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ], compute='_compute_overall_scores', store=True) manager_overall_star = fields.Selection([ ('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ], compute='_compute_overall_scores', store=True) hr_overall_star = fields.Selection([ ('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ], compute='_compute_overall_scores', store=True) finance_user_id = fields.Many2one('res.users', string="Finance Approved By", readonly=True) currency_id = fields.Many2one('res.currency',string='Currency',default=lambda self: self.env.company.currency_id.id) current_salary = fields.Monetary(string="Current Salary",related="employee_appraisal_id.contract_id.wage",currency_field='currency_id',store=True,readonly=True,) appraisal_percentage = fields.Float(string="Appraisal %") appraisal_amount = fields.Float(string="Appraisal Amount") new_salary = fields.Float(string="Revised Salary", compute="_compute_new_salary", store=True) finance_remarks = fields.Text(string="Finance Remarks") finance_date = fields.Datetime(string="Finance Approval Date", readonly=True) salary_update = fields.Boolean(string="Salary Update",default=False,readonly=True) contract_count = fields.Integer(string="Contracts",compute="_compute_contract_count") template_empl_rating_bool = fields.Boolean('Star Rating') template_empl_point_bool = fields.Boolean('Point Rating') disciplinary_ids = fields.Many2many( 'hr.employee.disciplinary',string='Disciplinary Actions',compute='_compute_disciplinary_ids') achievement_summary = fields.Html(string="Achievement Summary") employee_comments = fields.Text(string="Employee Comments") attachment_ids = fields.Many2many( 'ir.attachment', 'employee_appraisal_attachment_rel', 'appraisal_id', 'attachment_id', string='Supporting Documents' ) stage_color_class = fields.Char(compute="_compute_stage_color_class") invite_pip = fields.Boolean('Invite Pip',default=False) is_manager_reviewer = fields.Boolean(compute="_compute_user_roles",store=False) is_hr_reviewer = fields.Boolean(compute="_compute_user_roles",store=False) is_current_employee = fields.Boolean(compute='_compute_is_current_employee') @api.depends('employee_appraisal_id') def _compute_is_current_employee(self): current_employee = self.env.user.employee_id for rec in self: rec.is_current_employee = ( rec.employee_appraisal_id == current_employee ) def _compute_user_roles(self): current_user = self.env.user for rec in self: rec.is_manager_reviewer = ( rec.managerapp_id.user_id.id == current_user.id ) rec.is_hr_reviewer = ( rec.hr_apprai_id.user_id.id == current_user.id ) @api.depends('stage_id') def _compute_stage_color_class(self): for rec in self: if rec.stage_id.colour_seq == 1: rec.stage_color_class = 'success' elif rec.stage_id.colour_seq == 2: rec.stage_color_class = 'info' elif rec.stage_id.colour_seq == 3: rec.stage_color_class = 'warning' elif rec.stage_id.colour_seq == 4: rec.stage_color_class = 'primary' else: rec.stage_color_class = 'danger' @api.depends('employee_appraisal_id') def _compute_disciplinary_ids(self): for rec in self: rec.disciplinary_ids = self.env['hr.employee.disciplinary'].search([ ('employee_id', '=', rec.employee_appraisal_id.id) ]) @api.depends('template_id') def _compute_available_stages(self): for rec in self: rec.available_stage_ids = rec.template_id.stage_config_ids @api.depends( 'kra_line_ids.kpi_line_ids.rating_star', 'kra_line_ids.kpi_line_ids.manager_rating_star', 'kra_line_ids.kpi_line_ids.hr_rating_star', 'kra_line_ids.kpi_line_ids.employee_score', 'kra_line_ids.kpi_line_ids.manager_score', 'kra_line_ids.kpi_line_ids.hr_score', ) def _compute_overall_scores(self): for rec in self: employee_score = 0.0 manager_score = 0.0 hr_score = 0.0 # ================================== # STAR RATING MODE # ================================== if rec.template_empl_rating_bool: employee_ratings = [ int(kpi.rating_star) for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.rating_star ] manager_ratings = [ int(kpi.manager_rating_star) for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.manager_rating_star ] hr_ratings = [ int(kpi.hr_rating_star) for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.hr_rating_star ] employee_score = ( sum(employee_ratings) / len(employee_ratings) if employee_ratings else 0 ) manager_score = ( sum(manager_ratings) / len(manager_ratings) if manager_ratings else 0 ) hr_score = ( sum(hr_ratings) / len(hr_ratings) if hr_ratings else 0 ) rec.employee_overall_score = round(employee_score, 2) rec.manager_overall_score = round(manager_score, 2) rec.hr_overall_score = round(hr_score, 2) rec.employee_overall_star = str(round(employee_score)) if employee_score else '0' rec.manager_overall_star = str(round(manager_score)) if manager_score else '0' rec.hr_overall_star = str(round(hr_score)) if hr_score else '0' # Final Overall overall_avg = ( employee_score + manager_score + hr_score ) / 3 if (employee_score or manager_score or hr_score) else 0 rec.overall_score = round(overall_avg, 2) overall_star = str(round(overall_avg)) if overall_avg else '0' rec.overall_rating = overall_star rec.overall_rating_star = overall_star rec.overall_rating_value = RATING_VALUATION.get( overall_star, False ) # ================================== # POINT RATING MODE # ================================== elif rec.template_empl_point_bool: employee_scores = [ kpi.employee_score for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.employee_score is not False ] manager_scores = [ kpi.manager_score for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.manager_score is not False ] hr_scores = [ kpi.hr_score for kra in rec.kra_line_ids for kpi in kra.kpi_line_ids if kpi.hr_score is not False ] employee_score = ( sum(employee_scores) / len(employee_scores) if employee_scores else 0 ) manager_score = ( sum(manager_scores) / len(manager_scores) if manager_scores else 0 ) hr_score = ( sum(hr_scores) / len(hr_scores) if hr_scores else 0 ) rec.employee_overall_score = round(employee_score, 2) rec.manager_overall_score = round(manager_score, 2) rec.hr_overall_score = round(hr_score, 2) rec.employee_overall_star = '0' rec.manager_overall_star = '0' rec.hr_overall_star = '0' # Final Overall overall_avg = ( employee_score + manager_score + hr_score ) / 3 if (employee_score or manager_score or hr_score) else 0 rec.overall_score = round(overall_avg, 2) if overall_avg <= 1: rating = '1' elif overall_avg <= 2: rating = '2' elif overall_avg <= 3: rating = '3' elif overall_avg <= 4: rating = '4' else: rating = '5' rec.overall_rating = rating rec.overall_rating_star = rating rec.overall_rating_value = RATING_VALUATION.get( rating, False ) # ================================== # NO CONFIGURATION # ================================== else: rec.employee_overall_score = 0 rec.manager_overall_score = 0 rec.hr_overall_score = 0 rec.employee_overall_star = '0' rec.manager_overall_star = '0' rec.hr_overall_star = '0' rec.overall_score = 0 rec.overall_rating = '0' rec.overall_rating_star = '0' rec.overall_rating_value = False # # @api.onchange('overall_rating_value') # def _onchange_overall_rating_value(self): # for rec in self: # if rec.overall_rating_value == 'outstanding': # rec.appraisal_percentage = 20 # elif rec.overall_rating_value == 'exceed_expectation': # rec.appraisal_percentage = 15 # elif rec.overall_rating_value == 'meet_expectation': # rec.appraisal_percentage = 10 # elif rec.overall_rating_value == 'needs_improvements': # rec.appraisal_percentage = 5 # else: # rec.appraisal_percentage = 0 # @api.depends('manager_ids') # def _compute_manager_email(self): # for rec in self: # emails = rec.manager_ids.mapped('work_email') # rec.manager_email = ",".join(filter(None, emails)) @api.depends('managerapp_id') def _compute_managerapp_id(self): for rec in self: rec.manager_email = rec.managerapp_id.work_email or '' @api.onchange('employee_appraisal_id') def _onchange_employee_appraisal_id(self): self.managerapp_id = self.employee_appraisal_id.parent_id @api.depends('end_date') def _compute_is_readonly(self): today = fields.Date.today() for rec in self: if rec.end_date and today > rec.end_date: rec.is_readonly = True else: rec.is_readonly = False @api.depends( 'kra_line_ids.kpi_line_ids.rating_star', 'kra_line_ids.kpi_line_ids.manager_rating_star', 'kra_line_ids.kpi_line_ids.hr_rating_star', 'kra_line_ids.kpi_line_ids.employee_score', 'kra_line_ids.kpi_line_ids.manager_score', 'kra_line_ids.kpi_line_ids.hr_score', ) def _compute_total_scores(self): for rec in self: employee_total = 0.0 manager_total = 0.0 hr_total = 0.0 for kra in rec.kra_line_ids: for kpi in kra.kpi_line_ids: # Star Rating Mode if rec.template_empl_rating_bool: employee_total += float(kpi.rating_star or 0) manager_total += float(kpi.manager_rating_star or 0) hr_total += float(kpi.hr_rating_star or 0) # Point Rating Mode elif rec.template_empl_point_bool: employee_total += float(kpi.employee_score or 0) manager_total += float(kpi.manager_score or 0) hr_total += float(kpi.hr_score or 0) rec.total_employee_score = employee_total rec.total_manager_score = manager_total rec.total_hr_score = hr_total @api.depends('current_salary', 'appraisal_amount') def _compute_new_salary(self): for rec in self: rec.new_salary = rec.current_salary + rec.appraisal_amount @api.onchange('appraisal_percentage') def _onchange_appraisal_percentage(self): for rec in self: if rec.current_salary: rec.appraisal_amount = ( rec.current_salary * rec.appraisal_percentage ) / 100 def _move_to_next_stage(self): self.ensure_one() next_stage = self.env['employee.stage.config'].search( [('id', 'in', self.available_stage_ids.ids), ('seq', '>', self.stage_id.seq)], order='seq asc', limit=1 ) if next_stage: self.stage_id = next_stage.id def action_create_pip(self): self.ensure_one() pip = self.env['employee.pip'].create({ 'employee_id': self.employee_appraisal_id.id, 'manager_id': self.managerapp_id.id, 'appraisal_id': self.id, 'objective': 'Improve performance and achieve expected goals.', 'timeline': '90', }) return { 'type': 'ir.actions.act_window', 'res_model': 'employee.pip', 'res_id': pip.id, 'view_mode': 'form', 'target': 'current', } def _compute_contract_count(self): for rec in self: rec.contract_count = len(rec.employee_appraisal_id.contract_ids) def action_view_current_contract(self): self.ensure_one() contract = self.employee_appraisal_id.contract_id if not contract: return False return { 'type': 'ir.actions.act_window', 'name': 'Contract', 'res_model': 'hr.contract', 'view_mode': 'form', 'res_id': contract.id, 'target': 'current', } def action_update_contract_salary(self): for rec in self: contract = rec.employee_appraisal_id.contract_id if not contract: raise ValidationError( "No active contract found for employee.") if rec.new_salary <= 0: raise ValidationError("Revised Salary must be greater than zero.") contract.write({ 'wage': rec.new_salary }) rec.salary_update = True def action_finance_approve(self): self.ensure_one() if not self.finance_remarks: raise ValidationError( _('Please provide Finance Remarks.') ) if not self.current_salary: raise ValidationError( _('Please enter Current Salary.') ) if not self.appraisal_percentage: raise ValidationError( _('Please enter Appraisal Percentage.') ) # email_to = ",".join( # filter(None, [ # self.employee_appraisal_id.work_email, # self.managerapp_id.work_email, # self.creator_email # ]) # ) finance_head_group = self.env.ref('hrms_employee_appraisal.group_appraisal_finance_head') email_list = [] partner_ids = [] for user in finance_head_group.users: if user.partner_id.email: email_list.append(user.partner_id.email) if user.partner_id: partner_ids.append(user.partner_id.id) body_html = f"""
Hello,
Finance review has been completed for the appraisal of {self.employee_appraisal_id.name}.
The salary revision details are given below for further approval.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Current Salary | {self.current_salary or 0} |
| Appraisal Percentage | {self.appraisal_percentage or 0}% |
| Appraisal Amount | {self.appraisal_amount or 0} |
| Revised Salary | {self.new_salary or 0} |
| Finance Remarks | {self.finance_remarks or ''} |
Kindly review and proceed with the next level approval.
Regards,
Finance Team
Hello,
Finance Head has completed the appraisal review for {self.employee_appraisal_id.name}.
The compensation revision details have been reviewed and approved.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Current Salary | {self.current_salary or 0} |
| Appraisal % | {self.appraisal_percentage or 0}% |
| Increment Amount | {self.appraisal_amount or 0} |
| Revised Salary | {self.new_salary or 0} |
| Finance Head Remarks | {self.finance_head_remarks or ''} |
Kindly proceed with the next level approval.
Regards,
Finance Head
Hello Manager,
Employee appraisal is ready for Manager Evaluation.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Performance Period | {self.appraisal_period_id.appraisal_name or ''} |
Please complete manager evaluation.
Regards,
HR Team
Hello Team,
The employee has completed the self-assessment as part of the performance appraisal process. Your feedback and evaluation are now requested.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Appraisal Template | {self.template_id.name or ''} |
| Performance Period | {self.appraisal_period_id.appraisal_name or ''} |
Kindly review the employee's self-assessment and provide your feedback, evaluation, and recommendations within the appraisal timeline.
Your valuable input will contribute to the employee's overall performance review.
Regards,
Manager Team
Hello,
HR evaluation has been completed for the appraisal of {self.employee_appraisal_id.name}.
Kindly review the appraisal and take the necessary action.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Template | {self.template_id.name or ''} |
Regards,
HR Team
Hello,
HR Head review has been completed for the appraisal of {self.employee_appraisal_id.name}.
Kindly proceed with the next level approval.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| HR Head Remarks | {self.hr_head_remarks or ''} |
Regards,
HR Head
Hello Team,
{self.employee_appraisal_id.name} has completed the self-assessment. Please provide your feedback as part of the appraisal process.
Your feedback will help in evaluating the employee's overall performance.
Regards,
{self.employee_appraisal_id.name}
Dear {self.employee_appraisal_id.name},
Based on the recent performance appraisal review, your overall performance rating indicates that improvement is required in certain areas.
Therefore, you are requested to attend a Performance Improvement Plan (PIP) discussion meeting with Management and HR.
| Employee | {self.employee_appraisal_id.name or ''} |
| Department | {self.department_appraisal_id.name or ''} |
| Performance Period | {self.appraisal_period_id.appraisal_name or ''} |
| Overall Rating | {self.overall_rating or ''} |
During this meeting, we will discuss performance concerns, expectations, improvement objectives, and the Performance Improvement Plan (PIP) timeline.
Kindly acknowledge and attend the meeting as scheduled.
Regards,
Management Team