# Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import timedelta from pytz import timezone, UTC from odoo import api, fields, models, tools from odoo.tools import format_time class HrEmployeePublic(models.Model): _name = 'hr.employee.public' _description = 'Public Employee' _order = 'name' _auto = False _log_access = True # Include magic fields # Fields coming from hr.employee create_date = fields.Datetime(readonly=True) name = fields.Char(readonly=True) active = fields.Boolean(readonly=True) department_id = fields.Many2one('hr.department', readonly=True) member_of_department = fields.Boolean(compute='_compute_member_of_department', search='_search_part_of_department') job_id = fields.Many2one('hr.job', readonly=True) job_title = fields.Char(related='employee_id.job_title') company_id = fields.Many2one('res.company', readonly=True) address_id = fields.Many2one('res.partner', readonly=True) mobile_phone = fields.Char(readonly=True) work_phone = fields.Char(readonly=True) work_email = fields.Char(readonly=True) share = fields.Boolean(related='employee_id.share') phone = fields.Char(related='employee_id.phone') im_status = fields.Char(related='employee_id.im_status') email = fields.Char(related='employee_id.email') work_contact_id = fields.Many2one('res.partner', readonly=True) work_location_id = fields.Many2one('hr.work.location', readonly=True) work_location_name = fields.Char(related='employee_id.work_location_name') work_location_type = fields.Selection(related='employee_id.work_location_type') user_id = fields.Many2one('res.users', readonly=True) resource_id = fields.Many2one('resource.resource', readonly=True) tz = fields.Selection(related='resource_id.tz') color = fields.Integer(readonly=True) hr_presence_state = fields.Selection([ ('present', 'Present'), ('absent', 'Absent'), ('archive', 'Archived'), ('out_of_working_hour', 'Off-Hours')], compute='_compute_presence_state', default='out_of_working_hour') hr_icon_display = fields.Selection( selection='_get_selection_hr_icon_display', compute='_compute_presence_icon') show_hr_icon_display = fields.Boolean(compute='_compute_presence_icon') last_activity = fields.Date(compute="_compute_last_activity") last_activity_time = fields.Char(compute="_compute_last_activity") resource_calendar_id = fields.Many2one('resource.calendar', readonly=True) country_code = fields.Char(compute='_compute_country_code') # Manager-only fields is_manager = fields.Boolean(compute='_compute_is_manager') is_user = fields.Boolean(compute='_compute_is_user') employee_id = fields.Many2one('hr.employee', 'Employee', readonly=True) # hr.employee.public specific fields child_ids = fields.One2many('hr.employee.public', 'parent_id', string='Direct subordinates', readonly=True) image_1920 = fields.Image("Image", related='employee_id.image_1920', compute_sudo=True) image_1024 = fields.Image("Image 1024", related='employee_id.image_1024', compute_sudo=True) image_512 = fields.Image("Image 512", related='employee_id.image_512', compute_sudo=True) image_256 = fields.Image("Image 256", related='employee_id.image_256', compute_sudo=True) image_128 = fields.Image("Image 128", related='employee_id.image_128', compute_sudo=True) avatar_1920 = fields.Image("Avatar", related='employee_id.avatar_1920', compute_sudo=True) avatar_1024 = fields.Image("Avatar 1024", related='employee_id.avatar_1024', compute_sudo=True) avatar_512 = fields.Image("Avatar 512", related='employee_id.avatar_512', compute_sudo=True) avatar_256 = fields.Image("Avatar 256", related='employee_id.avatar_256', compute_sudo=True) avatar_128 = fields.Image("Avatar 128", related='employee_id.avatar_128', compute_sudo=True) parent_id = fields.Many2one('hr.employee.public', 'Manager', readonly=True) coach_id = fields.Many2one('hr.employee.public', 'Coach', readonly=True) user_partner_id = fields.Many2one(related='user_id.partner_id', related_sudo=False, string="User's partner") birthday_public_display_string = fields.Char("Public Date of Birth", related='employee_id.birthday_public_display_string') newly_hired = fields.Boolean('Newly Hired', compute='_compute_newly_hired', search='_search_newly_hired') def _get_selection_hr_icon_display(self): return self.env['hr.employee']._fields['hr_icon_display']._description_selection(self.env) def _compute_from_employee(self, field_names): if isinstance(field_names, str): field_names = [field_names] employees_sudo = self.sudo().env['hr.employee'].browse(self.ids) employee_per_id = {emp.id: emp for emp in employees_sudo} for public_employee in self: employee = employee_per_id[public_employee.id] for field_name in field_names: public_employee[field_name] = employee[field_name] @api.depends('user_id') def _compute_last_activity(self): for employee in self: tz = employee.tz # sudo: res.users - can access presence of accessible user if last_presence := employee.user_id.sudo().presence_ids.last_presence: last_activity_datetime = last_presence.replace(tzinfo=UTC).astimezone(timezone(tz)).replace(tzinfo=None) employee.last_activity = last_activity_datetime.date() if employee.last_activity == fields.Date.today(): employee.last_activity_time = format_time(self.env, last_presence, time_format='short') else: employee.last_activity_time = False else: employee.last_activity = False employee.last_activity_time = False def _compute_country_code(self): self._compute_from_employee('country_code') @api.depends_context('uid') @api.depends('parent_id') def _compute_is_manager(self): all_reports = self.env['hr.employee.public'].search([('id', 'child_of', self.env.user.employee_id.id)]).ids for employee in self: employee.is_manager = employee.id in all_reports @api.depends_context('uid') def _compute_is_user(self): user_employee_id = self.env.user.employee_id.id for employee in self: employee.is_user = employee.id == user_employee_id def _compute_presence_state(self): self._compute_from_employee('hr_presence_state') def _compute_presence_icon(self): self._compute_from_employee('hr_icon_display') self._compute_from_employee('show_hr_icon_display') def _compute_member_of_department(self): self._compute_from_employee('member_of_department') def _get_manager_only_fields(self): return [] def _search_part_of_department(self, operator, value): if operator != 'in': return NotImplemented user_employee = self._get_valid_employee_for_user() if not user_employee.department_id: return [('id', 'in', user_employee.ids)] return [('department_id', 'child_of', user_employee.department_id.ids)] def _get_valid_employee_for_user(self): user = self.env.user # retrieve the employee of the current active company for the user employee = user.employee_id if not employee: # search for all employees as superadmin to not get blocked by multi-company rules user_employees = user.employee_id.sudo().search([ ('user_id', '=', user.id) ]) # the default company employee is most likely the correct one, but fallback to the first if not available employee = user_employees.filtered(lambda r: r.company_id == user.company_id) or user_employees[:1] return employee @api.depends_context('uid') def _compute_manager_only_fields(self): manager_fields = self._get_manager_only_fields() for employee in self: if employee.is_manager: employee_sudo = employee.employee_id.sudo() for f in manager_fields: employee[f] = employee_sudo[f] else: for f in manager_fields: employee[f] = False def _compute_newly_hired(self): self._compute_from_employee('newly_hired') def _search_newly_hired(self, operator, value): if operator not in ('in', 'not in'): return NotImplemented new_hire_field = self.env['hr.employee']._get_new_hire_field() new_hires = self.env['hr.employee'].sudo().search([ (new_hire_field, '>', fields.Datetime.now() - timedelta(days=90)) ]) return [('id', operator, new_hires.ids)] @api.model def _get_fields(self): base_fields = ('id', 'employee_id', 'name', 'active') version_fields = self.env['hr.version']._fields return 'e.id AS id,e.id AS employee_id,e.name AS name,e.active AS active,' + ','.join( (f'v.{name}' if name in version_fields and version_fields[name].store else f'e.{name}') for name, field in self._fields.items() if name not in base_fields and field.store and field.column_type ) def init(self): tools.drop_view_if_exists(self.env.cr, self._table) self.env.cr.execute("""CREATE or REPLACE VIEW %s as ( SELECT %s FROM hr_employee e JOIN hr_version v ON v.id = e.current_version_id )""" % (self._table, self._get_fields())) def get_avatar_card_data(self, fields): return self.read(fields)