odoo18/addons_extensions/hrms_employee_appraisal/models/employee_appraisal.py

1309 lines
47 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')
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"""
<div>
<p>Hello,</p>
<p>
Finance review has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
The salary revision details are given below for further approval.
</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>Current Salary</b></td>
<td>{self.current_salary or 0}</td>
</tr>
<tr>
<td><b>Appraisal Percentage</b></td>
<td>{self.appraisal_percentage or 0}%</td>
</tr>
<tr>
<td><b>Appraisal Amount</b></td>
<td>{self.appraisal_amount or 0}</td>
</tr>
<tr>
<td><b>Revised Salary</b></td>
<td>{self.new_salary or 0}</td>
</tr>
<tr>
<td><b>Finance Remarks</b></td>
<td>{self.finance_remarks or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly review and proceed with the next level approval.
</p>
<br/>
<p>Regards,</p>
<p>Finance Team</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': ",".join(email_list),
'default_partner_ids': [(6, 0, list(set(partner_ids)))],
'default_subject': f'Finance Approval - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_next_stage': True,
'mark_finance_approved': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_finance_head(self):
self.ensure_one()
if not self.finance_head_remarks:
raise ValidationError(
_('Please provide Finance Head Remarks.')
)
# email_to = ",".join(
# filter(None, [
# self.employee_appraisal_id.work_email,
# self.managerapp_id.work_email,
# self.creator_email
# ])
# )
management_group = self.env.ref('hrms_employee_appraisal.group_appraisal_management')
email_list = []
partner_ids = []
for user in management_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"""
<div>
<p>Hello,</p>
<p>
Finance Head has completed the appraisal review for
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
The compensation revision details have been reviewed and approved.
</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>Current Salary</b></td>
<td>{self.current_salary or 0}</td>
</tr>
<tr>
<td><b>Appraisal %</b></td>
<td>{self.appraisal_percentage or 0}%</td>
</tr>
<tr>
<td><b>Increment Amount</b></td>
<td>{self.appraisal_amount or 0}</td>
</tr>
<tr>
<td><b>Revised Salary</b></td>
<td>{self.new_salary or 0}</td>
</tr>
<tr>
<td><b>Finance Head Remarks</b></td>
<td>{self.finance_head_remarks or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly proceed with the next level approval.
</p>
<br/>
<p>Regards,</p>
<p>Finance Head</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': ",".join(email_list),
'default_partner_ids': [(6, 0, list(set(partner_ids)))],
'default_subject': f'Finance Head Approval - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_next_stage': True,
'mark_finance_head_approved': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
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_name 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()
missing = []
for kra in self.kra_line_ids:
for kpi in kra.kpi_line_ids:
if self.template_empl_rating_bool and not kpi.manager_rating_star:
missing.append(_("Please give the Star rating for KPI '%s'.") % (kpi.question or ''))
if self.template_empl_point_bool and kpi.manager_score <= 0:
missing.append(_("Please enter the score for KPI '%s'.") % (kpi.question or ''))
if missing:
raise ValidationError("\n".join(missing))
if not self.manager_remarks:
raise ValidationError("Please give the Manager Remarks")
manager_email = self.managerapp_id.work_email or ''
employee_email = self.employee_appraisal_id.work_email or ''
creator_email = self.creator_email or ''
emails = ",".join(
filter(None, [
manager_email,
employee_email,
creator_email
])
)
partner_ids = []
# if self.managerapp_id.user_id.partner_id:
# partner_ids.append(self.managerapp_id.user_id.partner_id.id)
# if self.employee_appraisal_id.user_id.partner_id:
# partner_ids.append(self.employee_appraisal_id.user_id.partner_id.id)
if self.hr_apprai_id.user_id.partner_id:
partner_ids.append(self.hr_apprai_id.user_id.partner_id.id)
# creator = self.env['res.users'].search(
# [('email', '=', self.creator_email)],
# limit=1
# )
# if creator.partner_id:
# partner_ids.append(creator.partner_id.id)
body_html = f"""
<div>
<p>Hello Team,</p>
<p>
The employee has completed the self-assessment as part of the performance appraisal process.
Your feedback and evaluation are now requested.
</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>Appraisal Template</b></td>
<td>{self.template_id.name or ''}</td>
</tr>
<tr>
<td><b>Performance Period</b></td>
<td>{self.appraisal_period_id.appraisal_name or ''}</td>
</tr>
</table>
<br/>
<p>
Kindly review the employee's self-assessment and provide your feedback,
evaluation, and recommendations within the appraisal timeline.
</p>
<p>
Your valuable input will contribute to the employee's overall performance review.
</p>
<br/>
<p>
Regards,
</p>
<p>
Manager Team
</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': emails,
'default_partner_ids': [(6, 0, list(set(partner_ids)))],
'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_hr(self):
self.ensure_one()
missing = []
for kra in self.kra_line_ids:
for kpi in kra.kpi_line_ids:
if self.template_empl_rating_bool and not kpi.hr_rating_star:
missing.append(_("Please give the Star rating for KPI '%s'.") % (kpi.question or ''))
if self.template_empl_point_bool and kpi.hr_score <= 0:
missing.append(_("Please enter the score for KPI '%s'.") % (kpi.question or ''))
if missing:
raise ValidationError("\n".join(missing))
if not self.hr_remarks:
raise ValidationError ('Please Provide the Remarks')
email_to = self.managerapp_id.work_email or ''
partner_ids = []
if self.hr_head_apprai_id.user_id.partner_id:
partner_ids.append(self.hr_head_apprai_id.user_id.partner_id.id)
body_html = f"""
<div>
<p>Hello,</p>
<p>
HR evaluation has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
Kindly review the appraisal and take the necessary action.
</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>Template</b></td>
<td>{self.template_id.name or ''}</td>
</tr>
</table>
<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': email_to,
'default_partner_ids': [(6, 0, list(set(partner_ids)))],
'default_subject': f'HR Evaluation Completed - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_hr_next_stage': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_head_hr(self):
self.ensure_one()
if not self.hr_head_remarks:
raise ValidationError(
_('Please provide HR Head Remarks.')
)
# email_to = self.created_by_id.work_email or ''
# partner_ids = []
# if self.created_by_id.user_id.partner_id:
# partner_ids.append(self.created_by_id.user_id.partner_id.id)
finance_group = self.env.ref('hrms_employee_appraisal.group_appraisal_finance')
email_list = []
partner_ids = []
for user in finance_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"""
<div>
<p>Hello,</p>
<p>
HR Head review has been completed for the appraisal of
<b>{self.employee_appraisal_id.name}</b>.
</p>
<p>
Kindly proceed with the next level approval.
</p>
<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>HR Head Remarks</b></td>
<td>{self.hr_head_remarks or ''}</td>
</tr>
</table>
<br/>
<p>Regards,</p>
<p>HR Head</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': ",".join(email_list),
'default_partner_ids': [(6, 0, list(set(partner_ids)))],
'default_subject': f'HR Head Approval - {self.employee_appraisal_id.name}',
'default_body': body_html,
'move_hr_head_next_stage': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_send_colleague_feedback(self):
for rec in self:
# for kra in rec.kra_line_ids:
# for kpi in kra.kpi_line_ids:
# if rec.template_empl_rating_bool and not kpi.rating_star:
# raise ValidationError(_(
# "Please give the self rating for the KPI '%s' before sending the colleague feedback."
# ) % (kpi.question or ''))
# if rec.template_empl_point_bool and not kpi.employee_score <= 0:
# raise ValidationError(_("Please enter the self score for the KPI '%s' before sending the colleague feedback."
# ) % (kpi.question or ''))
missing = []
for kra in rec.kra_line_ids:
for kpi in kra.kpi_line_ids:
if rec.template_empl_rating_bool and not kpi.rating_star:
missing.append(_("Please give the self rating for KPI '%s'.") % (kpi.question or ''))
if rec.template_empl_point_bool and kpi.employee_score <= 0:
missing.append(_("Please enter the self score for KPI '%s'.") % (kpi.question or ''))
if missing:
raise ValidationError("\n".join(missing))
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
email_list = []
partner_ids = []
if rec.managerapp_id:
if rec.managerapp_id.work_email:
email_list.append(rec.managerapp_id.work_email)
if rec.managerapp_id.user_id.partner_id:
partner_ids.append(rec.managerapp_id.user_id.partner_id.id)
body_html = f"""
<div>
<p>Hello Team,</p>
<p>
{self.employee_appraisal_id.name} has completed the self-assessment.
Please provide your feedback as part of the appraisal process.
</p>
<p>
Your feedback will help in evaluating the employee's overall performance.
</p>
<br/>
<p>Regards,</p>
<p>{self.employee_appraisal_id.name}</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': ",".join(email_list),
'default_partner_ids': [(6, 0, partner_ids)],
'default_subject': f'Colleague Feedback Request - {self.employee_appraisal_id.name}',
'default_body': body_html,
'mark_colleague_feedback_sent': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
def action_initiate_pip(self):
self.ensure_one()
self.write({'invite_pip': True})
employee_email = self.employee_appraisal_id.work_email or ''
body_html = f"""
<div>
<p>Dear {self.employee_appraisal_id.name},</p>
<p>
Based on the recent performance appraisal review, your overall performance
rating indicates that improvement is required in certain areas.
</p>
<p>
Therefore, you are requested to attend a Performance Improvement Plan (PIP)
discussion meeting with Management and HR.
</p>
<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_name or ''}</td>
</tr>
<tr>
<td><b>Overall Rating</b></td>
<td>{self.overall_rating or ''}</td>
</tr>
</table>
<br/>
<p>
During this meeting, we will discuss performance concerns,
expectations, improvement objectives, and the Performance
Improvement Plan (PIP) timeline.
</p>
<p>
Kindly acknowledge and attend the meeting as scheduled.
</p>
<br/>
<p>Regards,</p>
<p>Management Team</p>
</div>
"""
ctx = {
'default_model': 'employee.appraisal.template.config',
'default_res_ids': [self.id],
'default_composition_mode': 'comment',
'default_email_to': employee_email,
'default_subject': 'Performance Improvement Plan (PIP) Meeting Notification',
'default_body': body_html,
'mark_invite_pip': True,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'view_mode': 'form',
'target': 'new',
'context': ctx,
}
@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'))