odoo18/addons_extensions/hr_employee_appraisal/models/employee_appraisal.py

513 lines
26 KiB
Python

from odoo import api, fields, models, _, tools
from odoo.exceptions import UserError, AccessError, ValidationError
import datetime
from dateutil import relativedelta
class employee_appraisal(models.Model):
_name = "employee.appraisal"
_description = "Employee"
_inherit = ['mail.thread', ]
def employee_appraisal_activation_date_update(self):
appraisal_periods = self.env['appraisal.period'].sudo().search([('activation_date','<=',fields.Date.today())])
employee_appraisals = self.env['employee.appraisal'].sudo().search([('appraisal_period_id','in',appraisal_periods.ids)])
for appraisal in employee_appraisals:
if appraisal.state == 'sent':
appraisal.button_send_employee()
# @api.depends('employee_rating_id.self_rating', 'employee_rating_id.manager_rating')
# def _avg_jr_rating(self):
# """
# Compute the total amounts of the SO.
# """
# for rec in self:
# leng_rating = len(rec.employee_rating_id)
# if leng_rating != 0:
# for rating in rec:
# emp_tot_rating = self_rating = manager_rating = 0
# for line in rating.employee_rating_id:
# self_rating += int(line.self_rating)
# manager_rating += int(line.manager_rating)
# # return self_rating
# rating.update({
# # 'self_rating':rating.round(self_rating),
# # 'manager_rating':rating.round(manager_rating),
# 'emp_avg_jr_rating': float(self_rating) / leng_rating,
# 'mng_avg_jr_rating': float(manager_rating) / leng_rating
# })
# else:
# rec.emp_avg_jr_rating = 0.0
# rec.mng_avg_jr_rating = 0.0
#
@api.depends('employee_job_requirement_id.emp_rating', 'employee_job_requirement_id.man_rating',
'employee_job_requirement_id.weightage')
def _avg_kra_rating(self):
"""
Compute the weighted average ratings for Employee and Manager based on the weightage of each KRA.
"""
for rec in self:
total_emp_rating = 0
total_man_rating = 0
total_weightage = 0
# Loop through each KRA
for line in rec.employee_job_requirement_id:
if line.weightage > 0: # Check if weightage is greater than 0
total_emp_rating += int(line.emp_rating) * line.weightage
total_man_rating += int(line.man_rating) * line.weightage
total_weightage += line.weightage
# Avoid division by zero if the total weightage is zero
if total_weightage != 0:
rec.emp_avg_kra_rating = total_emp_rating / total_weightage
rec.mng_avg_kra_rating = total_man_rating / total_weightage
else:
rec.emp_avg_kra_rating = 0.0
rec.mng_avg_kra_rating = 0.0
# def _get_total(self, cr, uid, ids, field_names, args, context=None):
# res = {}
# for line in self.browse(cr, uid, ids, context=context):
# res[line.id] = (line.mng_avg_jr_rating + line.mng_avg_kra_rating) / 2
# return res
#
@api.depends('employee_job_requirement_id.weightage')
def _tot_weightage(self):
"""
Compute the total amounts of the SO.
"""
for rec in self:
weightage = 0
for line in rec.employee_job_requirement_id:
weightage += line.weightage
rec.update({
'total_weightage': weightage
})
# we need a related field in order to be able to sort the employee by name
name = fields.Many2one('hr.employee', 'Employee Name', track_visibility='onchange', copy=False)
state = fields.Selection([('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
('sent', 'Sent to Manager'),
('emp_rating','Sent for Employee Rating'),
('manager_rating','Sent for Manager Rating'),
('to_approve', 'Sent to HR'),
('to_md', 'Sent to MD'),
], default='draft', string='Status', readonly=True, index=True, copy=False,
track_visibility='onchange')
appraisal_period_id = fields.Many2one('appraisal.period', 'Appraisal Period',
track_visibility='onchange', copy=False)
location = fields.Char('Location', track_visibility='onchange', copy=True)
designation = fields.Many2one('hr.job', 'Designation', track_visibility='onchange', copy=False, required=True)
user_id = fields.Many2one('res.users', related='name.user_id', string='Users', store=True,
track_visibility='onchange', copy=False)
department_id = fields.Many2one('hr.department', 'Department', track_visibility='onchange', copy=False)
reviewers_name = fields.Many2one('hr.employee', 'Reviewers Name', related="name.parent_id",store=True, track_visibility='onchange', copy=False)
todays_date = fields.Date(string='Date', default=fields.Date.today(), track_visibility='onchange', copy=False)
appraisal_active = fields.Boolean('Appraisal Active', copy=True)
# emp_tot_rating = fields.Integer('Emp Tot Rating', track_visibility='onchange', copy=False)
# emp_avg_jr_rating = fields.Float(string='Emp JR Average Rating', compute='_avg_jr_rating',
# track_visibility='onchange', copy=False)
# mng_avg_jr_rating = fields.Float(string='Manager JR Average Rating', compute='_avg_jr_rating',
# track_visibility='onchange', copy=False)
emp_avg_kra_rating = fields.Float(string='Employee KRA Average Rating', compute='_avg_kra_rating',
track_visibility='onchange', copy=False)
mng_avg_kra_rating = fields.Float(string='Manager KRA Average Rating', compute='_avg_kra_rating',
track_visibility='onchange', copy=False)
total_weightage = fields.Float(string='Total Weightage', compute='_tot_weightage', track_visibility='onchange',
copy=False)
comments_by_hr = fields.Char('Comments By HR', track_visibility='onchange', copy=False)
comments_by_md = fields.Char('Comments By MD', track_visibility='onchange', copy=False)
employee_comment_set = fields.Boolean('Employee comment set', track_visibility='onchange', copy=False)
employee_rating_id = fields.One2many('employee.rating', 'appraisal_id', 'Appraisal', track_visibility='onchange',
copy=True)
# 'overall_rating':fields.function(_get_total, string='Overall Manager Rating', type='float', track_visibility='onchange'),
overall_evaluation_of_performance = fields.Text('Overall Evaluation of Performance', track_visibility='onchange',
copy=False)
time_in_current_position = fields.Char('Time in current position', track_visibility='onchange', copy=False)
time_with_company = fields.Char('Time with Company', track_visibility='onchange', copy=False)
date = fields.Date('Date', track_visibility='onchange', copy=False)
evaluation_list = fields.Selection([
('far_exceeds_expectation', 'Far Exceeds Expectations'),
('exceed_expectation', 'Exceed Expectations'),
('met_expectation', 'Met Expectations'),
('below_expectation', 'Below Expectations'),
('never_met', 'Never Met'),
], string='Overall Evaluation', index=True, copy=False, track_visibility='onchange')
current_salary = fields.Float('Current Salary', copy=False)
proposed_merit_increase = fields.Integer('Proposed Merit Increase (%)', copy=False)
proposed_salary = fields.Float('Proposed Salary', copy=False)
proposed_promotion_list = fields.Selection([
('yes', 'Yes'),
('no', 'No'),
], string='Proposed Promotion', index=True, copy=False)
proposed_designation = fields.Char('Proposed Designation', copy=False)
employee_job_requirement_id = fields.One2many('employee.job.requirement', 'appraisal_id', 'Job Requirement',
track_visibility='onchange', copy=True)
appraisal_hr_id = fields.Many2one('res.users', string="HR Manager", compute='_compute_appraisal_hr_md')
appraisal_md_id = fields.Many2one('res.users', string="Managing Director", compute='_compute_appraisal_hr_md')
@api.depends('name')
def _compute_appraisal_hr_md(self):
hr_id = self.env['ir.config_parameter'].sudo().get_param('hr_employee_appraisal.appraisal_hr_id')
self.appraisal_hr_id = self.env['res.users'].sudo().browse(int(hr_id)) if hr_id else False
md_id = self.env['ir.config_parameter'].sudo().get_param('hr_employee_appraisal.appraisal_md_id')
self.appraisal_md_id = self.env['res.users'].sudo().browse(int(md_id)) if md_id else False
@api.constrains('total_weightage')
def constrain_total_weightage(self):
for record in self:
if record.total_weightage == 100:
return True
else:
raise UserError(_("Error: Total Weightage should be equal to 100"))
return True
# @api.model
# def default_get(self, default_fields):
# res = {}
# # srd = self.env['employee.rating']
# # ids = []
# # active_id = self._context.get('active_ids', [])
# # items_list = self.env['kpi.kra'].search([('kpi_kra_type','in',['kpi','both'])])
# # for item in items_list:
# # result = {'appraisal_id': active_id, 'name': item.id, 'state': 'draft'}
# # sr = srd.create(result)
# # ids.append(sr.id)
# # res['employee_rating_id'] = [(6, 0, ids)]
# res['state'] = 'draft'
# return res
@api.onchange('name')
def onchange_employee_name(self):
if self.name:
# self.reviewers_name = self.name.parent_id.id
self.department_id = self.name.department_id.id
self.designation = self.name.job_id.id
self.location = self.name.work_location_id.name
@api.onchange('designation')
def onchange_designation_id(self):
for rec in self:
# Clear existing records
rec.sudo().employee_job_requirement_id.unlink()
if rec.designation and rec.designation.kpi_kra_ids:
employee_job_requirement_ids = []
for kra in rec.designation.kpi_kra_ids:
# Create employee.job.requirement record
kra_data = {'appraisal_id': rec.id, 'kra_1': kra.id}
kra_create = self.env['employee.job.requirement'].create(kra_data)
# Create employee.rating records linked to the created employee.job.requirement
kpi_ids = []
for kpi in kra.kpi_question_ids:
kpi_data = {
'job_requirement_id': kra_create.id, # Link to the created employee.job.requirement
'appraisal_id': rec.id,
'name': kpi.id,
}
kpi_create = self.env['employee.rating'].create(kpi_data)
kpi_ids.append(kpi_create.id)
kra_create.write({'employee_rating_id': [(6, 0, kpi_ids)]})
employee_job_requirement_ids.append(kra_create.id)
rec.employee_job_requirement_id = [(6, 0, employee_job_requirement_ids)]
def button_send_employee(self):
for rec in self:
if rec.state == 'draft' and self.env.user.id != rec.appraisal_hr_id.id:
raise ValidationError(_("Only HR can send Appraisal to the employee"))
if rec.state == 'sent' and self.env.user.id != rec.reviewers_name.user_id.id:
raise ValidationError(_("Only Manager can send Appraisal to the employee in this stage"))
if rec.state=='draft':
if self.total_weightage == 100:
self.state = 'to_emp'
for line in self.employee_job_requirement_id:
line.status = 'to_emp'
template = self.env.ref('hr_employee_appraisal.employee_email_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
return True
else:
raise UserError(_('Error: Total Weightage should be equal to 100'))
elif rec.state == 'sent':
rec.state = 'emp_rating'
for line in self.employee_job_requirement_id:
line.status = 'emp_rating'
template = self.env.ref('hr_employee_appraisal.employee_email_submit_rating_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
return True
def button_refuse(self):
if self.state == 'sent':
self.state = 'to_emp'
for line in self.employee_job_requirement_id:
line.status = 'to_emp'
elif self.state == 'manager_rating':
self.state = 'emp_rating'
for line in self.employee_job_requirement_id:
line.status = 'emp_rating'
elif self.state == 'to_approve':
self.state = 'manager_rating'
for line in self.employee_job_requirement_id:
line.status = 'manager_rating'
elif self.state == 'to_md':
self.state = 'to_approve'
for line in self.employee_job_requirement_id:
line.status = 'to_approve'
# elif self.state=='to_approve':
# self.state='to_emp'
# for line in self.employee_rating_id:
# line.state='to_emp'
# for line in self.employee_job_requirement_id:
# line.status='to_emp'
# if self.state ='to_emp':
# state=manager_rating
# refuse = manager_rating
# if self.state ='mgr':
# statr=hr
# refuse=hr
def button_send_manager(self):
if self.env.user.id != self.name.user_id.id:
raise ValidationError(_("Only the Employee can submit to manager"))
for record in self.employee_job_requirement_id:
for rec in record.employee_rating_id:
if not rec.emp_kri_target:
raise ValidationError(_(f"Please provide your targets before submitting to Manager ({record.kra_1.name})"))
if not record.emp_kra_target:
raise ValidationError(_("Please provide your targets before submitting to Manager"))
for rec in self:
if rec.state == 'to_emp':
rec.state = 'sent'
template = self.env.ref('hr_employee_appraisal.manager_targets_submitted_email_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
# When the state is 'emp_rating', we need to ensure all fields are filled
if rec.state == 'emp_rating':
for record in rec.employee_job_requirement_id:
# Check if employee has filled the rating and comments in the job requirement (KRA)
if not record.emp_rating:
raise UserError(_('Please fill Employee Rating in the KRA: {}').format(record.kra_1.name))
if not record.sop:
raise UserError(_('Please fill Standards of Performance (SOP) in the KRA: {}').format(
record.kra_1.name))
for rating in record.employee_rating_id: # Iterate through related employee ratings
# Check if self-rating and employee comments are filled in the ratings
if not rating.self_rating:
raise UserError(_(f'Please fill your Self Rating for KPI: {rating.name.name}'))
if not rating.employee_comments:
raise UserError(_('Please fill your Employee Comments for KPI: {}').format(rating.name.name))
# After all checks, change the state to 'manager_rating'
self.state = 'manager_rating'
# Update the status for all job requirements to 'manager_rating'
for line in self.employee_job_requirement_id:
line.status = 'manager_rating'
# Send email to manager using the template
template = self.env.ref('hr_employee_appraisal.manager_email_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
return True
def button_send_hr(self):
# Ensure only the manager can send the appraisal to HR
if self.env.user.id != self.reviewers_name.user_id.id:
raise ValidationError(_("Only Manager can send to HR"))
# Check if all manager comments and ratings are filled in the employee ratings and job requirements
for record in self.employee_job_requirement_id:
for rating in record.employee_rating_id:
if not rating.manager_comments:
raise UserError(_(f'Please fill Manager Comments for KPI: {rating.name.name}'))
if not rating.manager_rating:
raise UserError(_('Please fill Manager Rating for KPI: {}').format(rating.name.name))
if not record.man_rating:
raise UserError(_('Please fill Manager Rating in the KRA: {}').format(record.kra_1.name))
if not record.results:
raise UserError(_('Please fill Results Achieved (Manager) for KRA: {}').format(record.kra_1.name))
# Change the state to 'to_approve' and update status for all ratings and job requirements
self.state = 'to_approve'
for line in self.employee_job_requirement_id:
line.status = 'to_approve'
# Send an email to HR
template = self.env.ref('hr_employee_appraisal.hr_email_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
return True
def button_send_md(self):
self.state = 'to_md'
for line in self.employee_rating_id:
line.state = 'to_md'
for line in self.employee_job_requirement_id:
line.status = 'to_md'
def button_back(self):
if self.state == 'to_approve':
self.state = 'sent'
for line in self.employee_rating_id:
line.state = 'sent'
for line in self.employee_job_requirement_id:
line.status = 'sent'
template = self.env.ref('hr_employee_appraisal.hr_reject_email_template')
self.env['mail.template'].browse(template.id).send_mail(self.id)
@api.onchange('current_salary', 'proposed_merit_increase')
def onchange_proposed_salary(self):
pro_merit = float(self.proposed_merit_increase)
if self.current_salary or pro_merit:
self.proposed_salary = self.current_salary + (self.current_salary * (pro_merit / 100))
@api.onchange('date')
def onchange_time_in_current_position(self):
# pdb.set_trace()
if self.date:
fmt = '%Y-%m-%d'
if self.name.doj:
d1 = self.name.doj
d2 = self.date
Diff = relativedelta.relativedelta(d2, d1)
self.time_in_current_position = str(Diff.years) + " Years " + str(Diff.months) + " Months " + str(
Diff.days) + " Days "
class employee_rating(models.Model):
_name = "employee.rating"
_description = "Part A"
_inherit = ['mail.thread', ]
state = fields.Selection([('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
('sent', 'Sent to Manager'),
('emp_rating','Sent for Employee Rating'),
('manager_rating','Sent for Manager Rating'),
('to_approve', 'Sent to HR'),
('to_md', 'Sent to MD'), ],
string='Status', related='job_requirement_id.status', track_visibility='onchange', copy=False)
appr_active_rel = fields.Boolean(related='appraisal_id.appraisal_active', string='Appraisal Active Related',
store=True, copy=False)
name = fields.Many2one('kpi.question', 'KPIs', track_visibility='onchange', copy=True)
emp_kri_target = fields.Text('Employee Target')
self_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Self Rating', track_visibility='onchange', copy=False)
employee_comments = fields.Char('Employee Comments', track_visibility='onchange', copy=False)
manager_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Manager Rating', track_visibility='onchange', copy=False)
manager_comments = fields.Char('Manager Comments', track_visibility='onchange', copy=False)
appraisal_id = fields.Many2one('employee.appraisal', 'Appraisal Id', related='job_requirement_id.appraisal_id',track_visibility='onchange', copy=True)
job_requirement_id = fields.Many2one('employee.job.requirement','Requirement Id', track_visibility='onchange', copy=True)
@api.constrains('self_rating')
def _self_rating(self):
record = self
if int(record.self_rating) <= 5 and int(record.self_rating) >= 1:
return True
else:
raise UserError(_('Error: Invalid JR Employee Rating'))
return True
@api.constrains('manager_rating')
def _manager_rating(self):
record = self
if int(record.manager_rating) <= 5 and int(record.manager_rating) >= 1:
return True
else:
raise UserError(_('Error: Invalid JR Manager Rating'))
return True
class employee_job_requirement(models.Model):
_name = "employee.job.requirement"
_description = "Part B"
@api.depends('employee_rating_id.self_rating', 'employee_rating_id.manager_rating')
def _avg_jr_rating(self):
"""
Compute the total amounts of the SO.
"""
for rec in self:
leng_rating = len(rec.employee_rating_id)
if leng_rating != 0:
for rating in rec:
emp_tot_rating = self_rating = manager_rating = 0
for line in rating.employee_rating_id:
self_rating += int(line.self_rating)
manager_rating += int(line.manager_rating)
# return self_rating
rating.update({
# 'self_rating':rating.round(self_rating),
# 'manager_rating':rating.round(manager_rating),
'emp_avg_jr_rating': float(self_rating) / leng_rating,
'mng_avg_jr_rating': float(manager_rating) / leng_rating
})
else:
rec.emp_avg_jr_rating = 0.0
rec.mng_avg_jr_rating = 0.0
status = fields.Selection(
[('draft', 'Draft Appraisal'), ('to_emp', 'Send to Employee'),
('sent', 'Sent to Manager'),
('emp_rating','Sent for Employee Rating'),
('manager_rating','Sent for Manager Rating'),
('to_approve', 'Sent to HR'),
('to_md', 'Sent to MD'),],
string='Status',related='appraisal_id.state',track_visibility='onchange', copy=False)
appr_active_kra_rel = fields.Boolean(related='appraisal_id.appraisal_active',
string='Appraisal Active KRA Related', store=True, copy=False)
kra_type = fields.Many2one('kra.types',related='kra_1.kra_type', string='KRA Type', index=True, track_visibility='onchange', copy=True)
kra_1 = fields.Many2one('kpi.kra','Job Requirements(KRA)',
help="(List each major job requirement and describe the key responsibilities of the function)",
track_visibility='onchange', copy=True)
emp_kra_target = fields.Text('Target', track_visibility='onchange', copy=True)
weightage = fields.Integer('Weightage', track_visibility='onchange', copy=False)
emp_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],string='Employee Rating (Rating between 1-5)', help="(Rating should be between 1 and 5)",
track_visibility='onchange', copy=False)
sop = fields.Text('Standards of Performance (Employee)',
help="(Indicate the quality of work that you have exhibited against the Key Roles that are delegated to you)",
track_visibility='onchange', copy=False)
man_rating = fields.Selection([(str(num), str(num)) for num in range(1, 6)],'Manager Rating (Rating between 1-5)', help="(Rating should be between 1 and 5)",
track_visibility='onchange', copy=False)
results = fields.Text('Results Achieved (Manager)',
help="(Describe the extent to which the employee has met the standards of performance expected for each major job function.)",
track_visibility='onchange', copy=False)
appraisal_id = fields.Many2one('employee.appraisal', 'Job Requirement Id', track_visibility='onchange',
copy=True)
employee_rating_id = fields.One2many('employee.rating', 'job_requirement_id', 'KPIs', track_visibility='onchange',
copy=True)
emp_avg_jr_rating = fields.Float(string='Emp JR Average Rating', compute='_avg_jr_rating',
track_visibility='onchange', copy=False)
mng_avg_jr_rating = fields.Float(string='Manager JR Average Rating', compute='_avg_jr_rating',
track_visibility='onchange', copy=False)
@api.constrains('man_rating')
def _weightage(self):
# pdb.set_trace()
for record in self:
if record.weightage != 0:
return True
else:
raise UserError(_('Error: Weightage should not be 0'))
return {}