odoo18/addons_extensions/hrms_employee_appraisal/models/employee_appraisal.py

645 lines
24 KiB
Python

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')
managerapp_id = fields.Many2one('hr.employee')
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')
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")
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)
current_salary = fields.Float(string="Current Salary")
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)
template_empl_rating_bool = fields.Boolean('Star Rating')
template_empl_point_bool = fields.Boolean('Point Rating')
@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('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(
[('seq', '>', self.stage_id.seq)],
order='seq asc',
limit=1
)
if next_stage:
self.stage_id = next_stage.id
def action_finance_approve(self):
for rec in self:
rec.write({
'state': 'management_team',
'finance_user_id': self.env.user.id,
'finance_date': fields.Datetime.now()
})
rec.message_post(
body=_(
"Finance appraisal approved."
)
)
def _send_manager_notification_mail(self):
self.ensure_one()
email_to = self.manager_email
if not email_to:
return
body_html = f"""
<div>
<p>Hello Manager,</p>
<p>
Employee appraisal is ready for Manager Evaluation.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td><b>Employee</b></td>
<td>{self.employee_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Department</b></td>
<td>{self.department_appraisal_id.name or ''}</td>
</tr>
<tr>
<td><b>Performance Period</b></td>
<td>{self.appraisal_period_id.appraisal_type_id.id or ''}</td>
</tr>
</table>
<br/>
<p>
Please complete manager evaluation.
</p>
<br/>
<p>
Regards,<br/>
HR Team
</p>
</div>
"""
mail_values = {
'subject': 'Manager Evaluation Notification',
'body_html': body_html,
'email_to': email_to,
}
self.env['mail.mail'].create(mail_values).send()
def check_colleague_feedback_deadline(self):
now = fields.Datetime.now()
records = self.search([
('college_end_date_time', '!=', False),
('college_end_date_time', '<=', now),
('state', '=', 'colleague_feedback')
])
for rec in records:
rec.write({
'state': 'in_progress'
})
rec.message_post(
body=_(
"Colleague feedback deadline completed automatically. "
"Stage moved to Manager Evaluation."
)
)
rec._send_manager_notification_mail()
return True
def action_sent_employee_appraisal(self):
self.ensure_one()
employee_email = self.employee_appraisal_id.work_email or ''
creator_email = self.creator_email or ''
emails = ",".join(
filter(None, [employee_email, creator_email])
)
body_html = f"""
<div>
<p>Hello,Team</p>
<p>
Your appraisal evaluation has been initiated.
</p>
<br/>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<td>
<b>Employee</b>
</td>
<td>
{self.employee_appraisal_id.name or ''}
</td>
</tr>
<tr>
<td>
<b>Template</b>
</td>
<td>
{self.template_id.name or ''}
</td>
</tr>
<tr>
<td>
<b>Performance Period</b>
</td>
<td>
{self.appraisal_period_id.appraisal_type_id.id or ''}
</td>
</tr>
</table>
<br/>
<p>
Please complete the self evaluation before deadline.
</p>
<br/>
<p>
Regards,
</p>
<p>
HR Team
</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': emails,
'default_subject': 'Employee Appraisal Notification',
'default_body': body_html,
'mark_appraisal_sent': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_confirm(self):
for rec in self:
rec.state = 'self_evaluation'
def action_confirm_manager(self):
for rec in self:
rec.state = 'hr_evaluation'
def action_confirm_hr(self):
for rec in self:
rec.state = 'finance_team'
def action_send_colleague_feedback(self):
for rec in self:
employees = self.env['hr.employee'].search([
('department_id', '=', rec.department_appraisal_id.id),
('id', '!=', rec.employee_appraisal_id.id)
])
vals = []
for emp in employees:
already_exists = self.env['colleague.feedback'].search([
('employee_appraisal_feed_id', '=', rec.id),
('colleague_feed_id', '=', emp.id)
], limit=1)
if not already_exists:
vals.append((0, 0, {
'colleague_feed_id': emp.id,
}))
rec.colleague_feed_ids = vals
rec.state = 'colleague_manager'
@api.onchange('template_id')
def _onchange_template_id(self):
self.kra_line_ids = [(5, 0, 0)]
if not self.template_id:
return
kra_vals = []
for kra in self.template_id.kra_ids:
kpi_vals = []
for kpi in kra.kpi_line_ids:
kpi_vals.append((0, 0, {
'question': kpi.name,
'description': kpi.kpi_description,
'kpi_line_weightage': kpi.kpi_weightage,
'manager_kpi_points': kpi.kpi_points,
'manager_kpi_stars': kpi.kpi_ratings,
}))
kra_vals.append((0, 0, {
'kra_id': kra.id,
'name': kra.name,
'description_line_kra': kra.description,
'kra_line_weightage': kra.kra_weightage,
'max_kra_points': kra.max_points,
'max_kra_stars': kra.max_star_rating,
'kpi_line_ids': kpi_vals,
}))
self.kra_line_ids = kra_vals
class ColleagueFeedBack(models.Model):
_name = 'colleague.feedback'
_description = 'Colleague Feedback'
colleague_feed_id = fields.Many2one('hr.employee', 'Employee', default=lambda self: self.env.user.employee_id.id)
feedback = fields.Char(string="Feedback")
employee_appraisal_feed_id = fields.Many2one('employee.appraisal.template.config')
state = fields.Selection([
('draft', 'Draft'),
('submitted', 'Submitted')
], default='draft')
submitted_date = fields.Datetime()
def action_submit_feedback(self):
for rec in self:
rec.write({
'state': 'submitted',
'submitted_date': fields.Datetime.now()
})
@api.constrains('colleague_feed_id', 'employee_appraisal_feed_id')
def _check_colleague_feed_id(self):
for rec in self:
duplicate = self.search([
('id', '!=', rec.id),
('colleague_feed_id', '=', rec.colleague_feed_id.id),
('employee_appraisal_feed_id', '=', rec.employee_appraisal_feed_id.id)
], limit=1)
if duplicate:
raise ValidationError(_('Colleague Feed Back already exists'))