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 {}