Files
odoo_source/addons/hr/models/hr_employee_base.py
T
Laurent Stukkens (LTU) 7a516973d3 [IMP] hr_contract: add employee type
Prior to this commit:

* All employees were treated the same way regarding the contractual point of vue.

From this commit on:

* Three employee types will be created:
 - Employee, which is supposed to be under contract
 - Student & Contractor which are both not supposed to have an employee contract (but are still use the employee as working for the company)

* Employee view and Contract History will take this consideration into account.

closes odoo/odoo#64931

Taskid: 2446000
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
2021-01-28 12:20:16 +00:00

188 lines
9.5 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from ast import literal_eval
from odoo import api, fields, models
from pytz import timezone, UTC, utc
from datetime import timedelta
from odoo.tools import format_time
class HrEmployeeBase(models.AbstractModel):
_name = "hr.employee.base"
_description = "Basic Employee"
_order = 'name'
name = fields.Char()
active = fields.Boolean("Active")
color = fields.Integer('Color Index', default=0)
department_id = fields.Many2one('hr.department', 'Department', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
job_id = fields.Many2one('hr.job', 'Job Position', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
job_title = fields.Char("Job Title", compute="_compute_job_title", store=True, readonly=False)
company_id = fields.Many2one('res.company', 'Company')
address_id = fields.Many2one('res.partner', 'Work Address', compute="_compute_address_id", store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
work_phone = fields.Char('Work Phone', compute="_compute_phones", store=True, readonly=False)
mobile_phone = fields.Char('Work Mobile')
work_email = fields.Char('Work Email')
work_location = fields.Char('Work Location')
user_id = fields.Many2one('res.users')
resource_id = fields.Many2one('resource.resource')
resource_calendar_id = fields.Many2one('resource.calendar', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
parent_id = fields.Many2one('hr.employee', 'Manager', compute="_compute_parent_id", store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
coach_id = fields.Many2one(
'hr.employee', 'Coach', compute='_compute_coach', store=True, readonly=False,
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
help='Select the "Employee" who is the coach of this employee.\n'
'The "Coach" has no specific rights or responsibilities by default.')
tz = fields.Selection(
string='Timezone', related='resource_id.tz', readonly=False,
help="This field is used in order to define in which timezone the resources will work.")
hr_presence_state = fields.Selection([
('present', 'Present'),
('absent', 'Absent'),
('to_define', 'To Define')], compute='_compute_presence_state', default='to_define')
last_activity = fields.Date(compute="_compute_last_activity")
last_activity_time = fields.Char(compute="_compute_last_activity")
hr_icon_display = fields.Selection([
('presence_present', 'Present'),
('presence_absent_active', 'Present but not active'),
('presence_absent', 'Absent'),
('presence_to_define', 'To define'),
('presence_undetermined', 'Undetermined')], compute='_compute_presence_icon')
employee_type = fields.Selection([
('employee', 'Employee'),
('student', 'Student'),
('trainee', 'Trainee'),
('contractor', 'Contractor'),
('freelance', 'Freelancer'),
], string='Employee Type', default='employee', required=True,
help="The employee type. Although the primary purpose may seem to categorize employees, this field has also an impact in the Contract History. Only Employee type is supposed to be under contract and will have a Contract History.")
@api.depends('user_id.im_status')
def _compute_presence_state(self):
"""
This method is overritten in several other modules which add additional
presence criterions. e.g. hr_attendance, hr_holidays
"""
# Check on login
check_login = literal_eval(self.env['ir.config_parameter'].sudo().get_param('hr.hr_presence_control_login', 'False'))
employee_to_check_working = self.filtered(lambda e: e.user_id.im_status == 'offline')
working_now_list = employee_to_check_working._get_employee_working_now()
for employee in self:
state = 'to_define'
if check_login:
if employee.user_id.im_status == 'online' or employee.last_activity:
state = 'present'
elif employee.user_id.im_status == 'offline' and employee.id not in working_now_list:
state = 'absent'
employee.hr_presence_state = state
@api.depends('user_id')
def _compute_last_activity(self):
presences = self.env['bus.presence'].search_read([('user_id', 'in', self.mapped('user_id').ids)], ['user_id', 'last_presence'])
# transform the result to a dict with this format {user.id: last_presence}
presences = {p['user_id'][0]: p['last_presence'] for p in presences}
for employee in self:
tz = employee.tz
last_presence = presences.get(employee.user_id.id, False)
if 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_activity_datetime, time_format='short')
else:
employee.last_activity_time = False
else:
employee.last_activity = False
employee.last_activity_time = False
@api.depends('parent_id')
def _compute_coach(self):
for employee in self:
manager = employee.parent_id
previous_manager = employee._origin.parent_id
if manager and (employee.coach_id == previous_manager or not employee.coach_id):
employee.coach_id = manager
elif not employee.coach_id:
employee.coach_id = False
@api.depends('job_id')
def _compute_job_title(self):
for employee in self.filtered('job_id'):
employee.job_title = employee.job_id.name
@api.depends('address_id')
def _compute_phones(self):
for employee in self:
if employee.address_id and employee.address_id.phone:
employee.work_phone = employee.address_id.phone
else:
employee.work_phone = False
@api.depends('company_id')
def _compute_address_id(self):
for employee in self:
address = employee.company_id.partner_id.address_get(['default'])
employee.address_id = address['default'] if address else False
@api.depends('department_id')
def _compute_parent_id(self):
for employee in self.filtered('department_id.manager_id'):
employee.parent_id = employee.department_id.manager_id
@api.depends('resource_calendar_id', 'hr_presence_state')
def _compute_presence_icon(self):
"""
This method compute the state defining the display icon in the kanban view.
It can be overriden to add other possibilities, like time off or attendances recordings.
"""
working_now_list = self.filtered(lambda e: e.hr_presence_state == 'present')._get_employee_working_now()
for employee in self:
if employee.hr_presence_state == 'present':
if employee.id in working_now_list:
icon = 'presence_present'
else:
icon = 'presence_absent_active'
elif employee.hr_presence_state == 'absent':
# employee is not in the working_now_list and he has a user_id
icon = 'presence_absent'
else:
# without attendance, default employee state is 'to_define' without confirmed presence/absence
# we need to check why they are not there
if employee.user_id:
# Display an orange icon on internal users.
icon = 'presence_to_define'
else:
# We don't want non-user employee to have icon.
icon = 'presence_undetermined'
employee.hr_icon_display = icon
@api.model
def _get_employee_working_now(self):
working_now = []
# We loop over all the employee tz and the resource calendar_id to detect working hours in batch.
all_employee_tz = self.mapped('tz')
for tz in all_employee_tz:
employee_ids = self.filtered(lambda e: e.tz == tz)
resource_calendar_ids = employee_ids.mapped('resource_calendar_id')
for calendar_id in resource_calendar_ids:
res_employee_ids = employee_ids.filtered(lambda e: e.resource_calendar_id.id == calendar_id.id)
start_dt = fields.Datetime.now()
stop_dt = start_dt + timedelta(hours=1)
from_datetime = utc.localize(start_dt).astimezone(timezone(tz or 'UTC'))
to_datetime = utc.localize(stop_dt).astimezone(timezone(tz or 'UTC'))
# Getting work interval of the first is working. Functions called on resource_calendar_id
# are waiting for singleton
work_interval = res_employee_ids[0].resource_calendar_id._work_intervals_batch(from_datetime, to_datetime)[False]
# Employee that is not supposed to work have empty items.
if len(work_interval._items) > 0:
# The employees should be working now according to their work schedule
working_now += res_employee_ids.ids
return working_now