1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/hr_appraisal/models/hr_appraisal.py
2024-12-10 09:04:09 +07:00

510 lines
27 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
import logging
import pytz
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools.misc import format_date
_logger = logging.getLogger(__name__)
class HrAppraisal(models.Model):
_name = "hr.appraisal"
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = "Employee Appraisal"
_order = 'state desc, id desc'
_rec_name = 'employee_id'
_mail_post_access = 'read'
def _get_default_employee(self):
if self.env.context.get('active_model') in ('hr.employee', 'hr.employee.public') and 'active_id' in self.env.context:
return self.env.context.get('active_id')
elif self.env.context.get('active_model') == 'res.users' and 'active_id' in self.env.context:
return self.env['res.users'].browse(self.env.context['active_id']).employee_id
if not self.env.user.has_group('hr_appraisal.group_hr_appraisal_user'):
return self.env.user.employee_id
active = fields.Boolean(default=True)
employee_id = fields.Many2one(
'hr.employee', required=True, string='Employee', index=True,
default=_get_default_employee)
employee_user_id = fields.Many2one('res.users', string="Employee User", related='employee_id.user_id')
company_id = fields.Many2one('res.company', related='employee_id.company_id', store=True)
department_id = fields.Many2one(
'hr.department', compute='_compute_department_id', string='Department', store=True)
image_128 = fields.Image(related='employee_id.image_128')
image_1920 = fields.Image(related='employee_id.image_1920')
avatar_128 = fields.Image(related='employee_id.avatar_128')
avatar_1920 = fields.Image(related='employee_id.avatar_1920')
last_appraisal_id = fields.Many2one('hr.appraisal', related='employee_id.last_appraisal_id')
last_appraisal_date = fields.Date(related='employee_id.last_appraisal_date')
employee_appraisal_count = fields.Integer(related='employee_id.appraisal_count')
uncomplete_goals_count = fields.Integer(related='employee_id.uncomplete_goals_count')
employee_feedback_template = fields.Html(default=lambda self: self.env.company.appraisal_employee_feedback_template, compute='_compute_feedback_templates', translate=True)
manager_feedback_template = fields.Html(default=lambda self: self.env.company.appraisal_manager_feedback_template, compute='_compute_feedback_templates', translate=True)
date_close = fields.Date(
string='Appraisal Date', help='Date of the appraisal, automatically updated when the appraisal is Done or Cancelled.', required=True, index=True,
default=lambda self: datetime.date.today() + relativedelta(months=+1))
state = fields.Selection(
[('new', 'To Confirm'),
('pending', 'Confirmed'),
('done', 'Done'),
('cancel', "Cancelled")],
string='Status', tracking=True, required=True, copy=False,
default='new', index=True, group_expand='_group_expand_states')
manager_ids = fields.Many2many(
'hr.employee', 'appraisal_manager_rel', 'hr_appraisal_id',
context={'active_test': False},
domain="[('id', '!=', employee_id), ('active', '=', 'True'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]")
manager_user_ids = fields.Many2many('res.users', string="Manager Users", compute='_compute_user_manager_rights')
meeting_ids = fields.Many2many('calendar.event', string='Meetings')
meeting_count_display = fields.Char(string='Meeting Count', compute='_compute_meeting_count')
date_final_interview = fields.Date(string="Final Interview", compute='_compute_final_interview')
is_manager = fields.Boolean(compute='_compute_user_manager_rights')
employee_autocomplete_ids = fields.Many2many('hr.employee', compute='_compute_user_manager_rights')
waiting_feedback = fields.Boolean(
string="Waiting Feedback from Employee/Managers", compute='_compute_waiting_feedback')
employee_feedback = fields.Html(compute='_compute_employee_feedback', store=True, readonly=False)
show_employee_feedback_full = fields.Boolean(compute='_compute_show_employee_feedback_full')
manager_feedback = fields.Html(compute='_compute_manager_feedback', store=True, readonly=False)
show_manager_feedback_full = fields.Boolean(compute='_compute_show_manager_feedback_full')
employee_feedback_published = fields.Boolean(string="Employee Feedback Published", default=True, tracking=True)
manager_feedback_published = fields.Boolean(string="Manager Feedback Published", default=True, tracking=True)
can_see_employee_publish = fields.Boolean(compute='_compute_buttons_display')
can_see_manager_publish = fields.Boolean(compute='_compute_buttons_display')
assessment_note = fields.Many2one('hr.appraisal.note', string="Final Rating", help="This field is not visible to the Employee.", domain="[('company_id', '=', company_id)]")
note = fields.Html(string="Private Note", help="The content of this note is not visible by the Employee.")
appraisal_plan_posted = fields.Boolean()
appraisal_properties = fields.Properties("Properties", definition="department_id.appraisal_properties_definition", precompute=False)
@api.depends('employee_id')
def _compute_department_id(self):
for appraisal in self:
if appraisal.employee_id:
appraisal.department_id = appraisal.employee_id.department_id
else:
appraisal.department_id = False
@api.depends_context('uid')
@api.depends('employee_id', 'manager_ids')
def _compute_buttons_display(self):
new_appraisals = self.filtered(lambda a: a.state == 'new')
new_appraisals.update({
'can_see_employee_publish': False,
'can_see_manager_publish': False,
})
user_employee = self.env.user.employee_id
is_manager = self.env.user.user_has_groups('hr_appraisal.group_hr_appraisal_user')
for appraisal in self:
# Appraisal manager can edit feedback in draft state
appraisal.can_see_employee_publish = (user_employee == appraisal.employee_id) or \
(user_employee.id in appraisal.manager_ids.ids and appraisal.state == 'new')
appraisal.can_see_manager_publish = user_employee.id in appraisal.manager_ids.ids
for appraisal in self - new_appraisals:
if is_manager and not appraisal.can_see_employee_publish and not appraisal.can_see_manager_publish:
appraisal.can_see_employee_publish, appraisal.can_see_manager_publish = True, True
@api.depends_context('uid')
@api.depends('manager_ids', 'employee_id', 'employee_id.parent_id')
def _compute_user_manager_rights(self):
self.employee_autocomplete_ids = self.env.user.get_employee_autocomplete_ids()
for appraisal in self:
appraisal.manager_user_ids = appraisal.manager_ids.user_id
appraisal.is_manager =\
self.user_has_groups('hr_appraisal.group_hr_appraisal_user')\
or self.env.user.employee_id in (appraisal.manager_ids | appraisal.employee_id.parent_id)
@api.depends_context('uid')
@api.depends('employee_id', 'employee_feedback_published')
def _compute_show_employee_feedback_full(self):
for appraisal in self:
is_appraisee = appraisal.employee_id.user_id == self.env.user
appraisal.show_employee_feedback_full = is_appraisee and not appraisal.employee_feedback_published
@api.depends_context('uid')
@api.depends('manager_ids', 'manager_feedback_published')
def _compute_show_manager_feedback_full(self):
for appraisal in self:
is_appraiser = self.env.user in appraisal.manager_ids.user_id
appraisal.show_manager_feedback_full = is_appraiser and not appraisal.manager_feedback_published
@api.depends('department_id')
def _compute_employee_feedback(self):
for appraisal in self.filtered(lambda a: a.state in ['new', 'pending']):
employee_template = appraisal.department_id.employee_feedback_template if appraisal.department_id.custom_appraisal_templates \
else appraisal.company_id.appraisal_employee_feedback_template
if appraisal.state == 'new':
appraisal.employee_feedback = employee_template
else:
appraisal.employee_feedback = appraisal.employee_feedback or employee_template
@api.depends('department_id')
def _compute_manager_feedback(self):
for appraisal in self.filtered(lambda a: a.state in ['new', 'pending']):
manager_template = appraisal.department_id.manager_feedback_template if appraisal.department_id.custom_appraisal_templates \
else appraisal.company_id.appraisal_manager_feedback_template
if appraisal.state == 'new':
appraisal.manager_feedback = manager_template
else:
appraisal.manager_feedback = appraisal.manager_feedback or manager_template
@api.depends('department_id', 'company_id')
def _compute_feedback_templates(self):
for appraisal in self:
appraisal.employee_feedback_template = appraisal.department_id.employee_feedback_template if appraisal.department_id.custom_appraisal_templates \
else appraisal.company_id.appraisal_employee_feedback_template
appraisal.manager_feedback_template = appraisal.department_id.manager_feedback_template if appraisal.department_id.custom_appraisal_templates \
else appraisal.company_id.appraisal_manager_feedback_template
@api.depends('employee_feedback_published', 'manager_feedback_published')
def _compute_waiting_feedback(self):
for appraisal in self:
appraisal.waiting_feedback = not appraisal.employee_feedback_published or not appraisal.manager_feedback_published
@api.depends_context('uid')
@api.depends('meeting_ids.start')
def _compute_final_interview(self):
today = fields.Date.today()
user_tz = self.env.user.tz or self.env.context.get('tz')
user_pytz = pytz.timezone(user_tz) if user_tz else pytz.utc
with_meeting = self.filtered('meeting_ids')
(self - with_meeting).date_final_interview = False
for appraisal in with_meeting:
all_dates = appraisal.meeting_ids.mapped('start')
min_date, max_date = min(all_dates), max(all_dates)
if min_date.date() >= today:
appraisal.date_final_interview = min_date.astimezone(user_pytz)
else:
appraisal.date_final_interview = max_date.astimezone(user_pytz)
@api.depends_context('lang')
@api.depends('meeting_ids')
def _compute_meeting_count(self):
today = fields.Date.today()
for appraisal in self:
count = len(appraisal.meeting_ids)
if not count:
appraisal.meeting_count_display = _('No Meeting')
elif count == 1:
appraisal.meeting_count_display = _('1 Meeting')
elif appraisal.date_final_interview >= today:
appraisal.meeting_count_display = _('Next Meeting')
else:
appraisal.meeting_count_display = _('Last Meeting')
def _group_expand_states(self, states, domain, order):
return [key for key, val in self._fields['state'].selection]
@api.onchange('employee_id')
def _onchange_employee_id(self):
self = self.sudo() # fields are not on the employee public
if self.employee_id:
manager = self.employee_id.parent_id
self.manager_ids = manager if manager != self.employee_id else False
self.department_id = self.employee_id.department_id
def subscribe_employees(self):
for appraisal in self:
partners = appraisal.manager_ids.mapped('related_partner_id') | appraisal.employee_id.related_partner_id
appraisal.message_subscribe(partner_ids=partners.ids)
def send_appraisal(self):
for appraisal in self:
confirmation_mail_template = appraisal.company_id.appraisal_confirm_mail_template
mapped_data = {
**{appraisal.employee_id: confirmation_mail_template},
**{manager: confirmation_mail_template for manager in appraisal.manager_ids}
}
for employee, mail_template in mapped_data.items():
if not employee.work_email or not self.env.user.email or not mail_template:
continue
ctx = {
'employee_to_name': appraisal.employee_id.name,
'recipient_users': employee.user_id,
'url': '/mail/view?model=%s&res_id=%s' % ('hr.appraisal', appraisal.id),
}
mail_template = mail_template.with_context(**ctx)
subject = mail_template._render_field('subject', appraisal.ids)[appraisal.id]
body = mail_template._render_field('body_html', appraisal.ids)[appraisal.id]
# post the message
mail_values = {
'email_from': self.env.user.email_formatted,
'author_id': self.env.user.partner_id.id,
'model': None,
'res_id': None,
'subject': subject,
'body_html': body,
'auto_delete': True,
'email_to': employee.work_email
}
template_ctx = {
'model_description': self.env['ir.model']._get('hr.appraisal').display_name,
'message': self.env['mail.message'].sudo().new(dict(body=mail_values['body_html'], record_name=_("Appraisal Request"))),
'company': self.env.company,
}
body = self.env['ir.qweb']._render('mail.mail_notification_light', template_ctx, minimal_qcontext=True, raise_if_not_found=False)
if body:
mail_values['body_html'] = self.env['mail.render.mixin']._replace_local_links(body)
else:
_logger.warning('QWeb template mail.mail_notification_light not found when sending appraisal confirmed mails. Sending without layouting.')
self.env['mail.mail'].sudo().create(mail_values)
from_cron = 'from_cron' in self.env.context
# When cron creates appraisal, it creates specific activities
# In this case, no need to create activities, not to be repetitive
if employee.user_id and not from_cron:
appraisal.activity_schedule(
'mail.mail_activity_data_todo', appraisal.date_close,
summary=_('Appraisal Form to Fill'),
note=_('Fill appraisal for %s', appraisal.employee_id._get_html_link()),
user_id=employee.user_id.id)
def action_cancel(self):
self.state = 'cancel'
@api.model_create_multi
def create(self, vals_list):
appraisals = super().create(vals_list)
appraisals_to_send = self.env['hr.appraisal']
current_date = datetime.date.today()
for appraisal, vals in zip(appraisals, vals_list):
if vals.get('state') and vals['state'] == 'pending':
appraisals_to_send |= appraisal
if vals.get('state') and vals['state'] == 'new':
appraisal.employee_id.sudo().write({
'last_appraisal_id': appraisal.id,
'last_appraisal_date': current_date,
})
appraisals_to_send.send_appraisal()
appraisals.subscribe_employees()
return appraisals
def _check_access(self, fields):
fields = set(fields)
if {'manager_feedback', 'manager_feedback_published'} & fields:
if not all(a.can_see_manager_publish for a in self):
raise UserError(_('The manager feedback cannot be changed by an employee.'))
if {'employee_feedback'} & fields:
if not all(a.can_see_employee_publish for a in self):
raise UserError(_('The employee feedback cannot be changed by managers.'))
def write(self, vals):
self._check_access(vals.keys())
force_published = self.env['hr.appraisal']
if vals.get('employee_feedback_published'):
user_employees = self.env.user.employee_ids
force_published = self.filtered(lambda a: (a.is_manager) and not (a.employee_feedback_published or a.employee_id in user_employees))
current_date = datetime.date.today()
if 'state' in vals and vals['state'] in ['pending', 'done']:
for appraisal in self:
appraisal.employee_id.sudo().write({
'last_appraisal_id': appraisal.id,
'last_appraisal_date': current_date,
})
if 'state' in vals and vals['state'] == 'pending':
for appraisal in self:
if appraisal.state != 'done':
vals['employee_feedback_published'] = False
vals['manager_feedback_published'] = False
appraisal.activity_feedback(['mail.mail_activity_data_meeting', 'mail.mail_activity_data_todo'])
appraisal.send_appraisal()
if 'state' in vals and vals['state'] == 'done':
vals['employee_feedback_published'] = True
vals['manager_feedback_published'] = True
vals['date_close'] = current_date
self.activity_feedback(['mail.mail_activity_data_meeting', 'mail.mail_activity_data_todo'])
self._appraisal_plan_post()
body = _("The appraisal's status has been set to Done by %s", self.env.user.name)
appraisal.message_notify(
body=body,
subject=_("Your Appraisal has been completed"),
partner_ids=appraisal.message_partner_ids.ids,
)
appraisal.message_post(body=body)
if 'state' in vals and vals['state'] == 'cancel':
self.meeting_ids.unlink()
self.activity_unlink(['mail.mail_activity_data_meeting', 'mail.mail_activity_data_todo'])
previous_managers = {}
if 'manager_ids' in vals:
previous_managers = {x: y for x, y in self.mapped(lambda a: (a.id, a.manager_ids))}
result = super(HrAppraisal, self).write(vals)
if force_published:
for appraisal in force_published:
role = _('Manager') if self.env.user.employee_id in appraisal.manager_ids else _('Appraisal Officer')
appraisal.message_post(body=_('%(user)s decided, as %(role)s, to publish the employee\'s feedback', user=self.env.user.name, role=role))
if 'manager_ids' in vals:
self._sync_meeting_attendees(previous_managers)
return result
def _appraisal_plan_post(self):
odoobot = self.env.ref('base.partner_root')
dates = self.employee_id.sudo()._upcoming_appraisal_creation_date()
for appraisal in self:
# The only ongoing appraisal is the current one
if not appraisal.appraisal_plan_posted and appraisal.company_id.appraisal_plan and appraisal.employee_id.sudo().ongoing_appraisal_count == 1:
date = dates[appraisal.employee_id.id]
formated_date = format_date(self.env, date, date_format="MMM d y")
body = _('Thanks to your Appraisal Plan, without any new manual Appraisal, the new Appraisal will be automatically created on %s.', formated_date)
appraisal._message_log(body=body, author_id=odoobot.id)
appraisal.appraisal_plan_posted = True
def _generate_activities(self):
today = fields.Date.today()
for appraisal in self:
employee = appraisal.employee_id
managers = appraisal.manager_ids
last_appraisal_months = employee.last_appraisal_date and (
today.year - employee.last_appraisal_date.year)*12 + (today.month - employee.last_appraisal_date.month)
if employee.user_id:
# an appraisal has been just created
if employee.appraisal_count == 1:
months = (appraisal.date_close.year - employee.create_date.year) * \
12 + (appraisal.date_close.month - employee.create_date.month)
note = _("You arrived %s months ago. Your appraisal is created and you can fill it here.", months)
else:
note = _("Your last appraisal was %s months ago. Your appraisal is created and you can fill it here.", last_appraisal_months)
appraisal.with_context(mail_activity_quick_update=True).activity_schedule(
'mail.mail_activity_data_todo', today,
summary=_('Appraisal to fill'),
note=note, user_id=employee.user_id.id)
for manager in managers.filtered('user_id'):
if employee.appraisal_count == 1:
note = _(
"The employee %s arrived %s months ago. The appraisal is created and you can fill it here.",
employee._get_html_link(), months)
else:
note = _(
"The last appraisal of %s was %s months ago. The appraisal is created and you can fill it here.",
appraisal.employee_id._get_html_link(), last_appraisal_months)
appraisal.with_context(mail_activity_quick_update=True).activity_schedule(
'mail.mail_activity_data_todo', today,
summary=_('Appraisal for %s to fill', employee.name),
note=note, user_id=manager.user_id.id)
def _sync_meeting_attendees(self, manager_ids):
for appraisal in self.filtered('meeting_ids'):
previous_managers = manager_ids.get(appraisal.id, self.env['hr.employee'])
to_add = self.manager_ids - previous_managers
to_del = previous_managers - self.manager_ids
if to_add or to_del:
appraisal.meeting_ids.write({
'partner_ids': [
*[(3, x) for x in to_del.mapped('related_partner_id').ids],
*[(4, x) for x in to_add.mapped('related_partner_id').ids],
]
})
@api.ondelete(at_uninstall=False)
def _unlink_if_new_or_cancel(self):
if any(appraisal.state not in ['new', 'cancel'] for appraisal in self):
raise UserError(_("You cannot delete appraisal which is not in draft or canceled state"))
def read(self, fields=None, load='_classic_read'):
check_feedback = set(fields) & {'manager_feedback', 'employee_feedback'}
check_notes = set(fields) & {'note', 'assessment_note'}
if check_feedback:
fields = fields + ['can_see_employee_publish', 'can_see_manager_publish', 'employee_feedback_published', 'manager_feedback_published']
if check_notes:
fields = fields + ['employee_id']
records = super().read(fields, load)
if check_feedback:
for appraisal in records:
if not appraisal['can_see_employee_publish'] and not appraisal['employee_feedback_published']:
appraisal['employee_feedback'] = _('Unpublished')
if not appraisal['can_see_manager_publish'] and not appraisal['manager_feedback_published']:
appraisal['manager_feedback'] = _('Unpublished')
if check_notes:
for appraisal in records:
if appraisal['employee_id'] == self.env.user.employee_id.id:
appraisal['note'] = _('Note')
appraisal['assessment_note'] = False
return records
@api.model
def _read_group_check_field_access_rights(self, field_names):
super()._read_group_check_field_access_rights(field_names)
if {'manager_feedback', 'employee_feedback'}.intersection(field_names):
raise UserError(_('Such grouping is not allowed.'))
def mapped(self, func):
if func and isinstance(func, str):
self._check_access(set(func.split('.')))
return super().mapped(func)
@api.model
def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None):
fields_list = {term[0] for term in domain if isinstance(term, (tuple, list))}
self._check_access(fields_list)
return super()._search(domain, offset, limit, order, access_rights_uid)
def filtered_domain(self, domain):
fields_list = {term[0] for term in domain if isinstance(term, (tuple, list))}
self._check_access(fields_list)
return super().filtered_domain(domain)
def action_calendar_event(self):
self.ensure_one()
partners = self.manager_ids.mapped('related_partner_id') | self.employee_id.related_partner_id | self.env.user.partner_id
action = self.env["ir.actions.actions"]._for_xml_id("calendar.action_calendar_event")
action['context'] = {
'default_partner_ids': partners.ids,
'default_res_model': 'hr.appraisal',
'default_res_id': self.id,
'default_name': _('Appraisal of %s', self.employee_id.name),
}
return action
def action_confirm(self):
self.state = 'pending'
def action_done(self):
self.state = 'done'
def action_back(self):
self.state = 'new'
def action_open_employee_appraisals(self):
self.ensure_one()
view_id = self.env.ref('hr_appraisal.hr_appraisal_view_tree_orderby_create_date').id
return {
'name': _('Previous Appraisals'),
'res_model': 'hr.appraisal',
'view_mode': 'tree,kanban,form,gantt,calendar,activity',
'views': [(view_id, 'tree'), (False, 'kanban'), (False, 'form'), (False, 'gantt'), (False, 'calendar'), (False, 'activity')],
'domain': [('employee_id', '=', self.employee_id.id)],
'type': 'ir.actions.act_window',
'target': 'current',
'context': {
'search_default_groupby_date_close': True,
}
}
def action_open_goals(self):
self.ensure_one()
return {
'name': _("%s's Goals", self.employee_id.name),
'view_mode': 'kanban,tree,form,graph',
'res_model': 'hr.appraisal.goal',
'type': 'ir.actions.act_window',
'target': 'current',
'domain': [('employee_id', '=', self.employee_id.id)],
'context': {'default_employee_id': self.employee_id.id},
}
def action_send_appraisal_request(self):
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'request.appraisal',
'target': 'new',
'name': _('Appraisal Request'),
'context': {'default_appraisal_id': self.id},
}