[MERGE] hr*: Introduce the public employee profile
Purpose ======= 1/ Robustness & security: right now it is not easy to understand and do something clean in term of security (hr people vs employees, private info vs public). A HR officer doesn't know if he can write something on the chatter. Currently, a note will be visible for all the employees who have access to the employee form view for example. 2/ In term of business, it makes sense to let a hr manages payroll stuff (contract, employees private information, ... and other employee see public information (résumé and work information) Specification ============= Introduce 2 new models: - hr.employee.base (AbstractModel): This represents the basic skeleton model on which the shared fields and methods between the public and the private employees models. - hr.employee.public (_auto=False): This is a sql view based on the employee values, readable for an internal user (i.e. an employee). The model hr.employee is not readable anymore for an employee. There are now 3 ways to access the employee data: 1/ From the hr.employee views. HR officer access rights are required 2/ From the public profile. The public data for an employee are accessible but can't be modified. 3/ From the 'My Profile' menu. A classic employee can access its own data from there, and can modify them. TaskID: 1961151 closes odoo/odoo#33725 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
@@ -26,6 +26,12 @@
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/hr_plan_wizard_views.xml',
|
||||
'wizard/hr_departure_wizard_views.xml',
|
||||
'views/hr_job_views.xml',
|
||||
'views/hr_plan_views.xml',
|
||||
'views/hr_employee_category_views.xml',
|
||||
'views/hr_employee_public_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'views/hr_department_views.xml',
|
||||
'views/hr_views.xml',
|
||||
'views/hr_templates.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr
|
||||
from . import res_config_settings
|
||||
from . import hr_employee_base
|
||||
from . import hr_employee
|
||||
from . import hr_employee_category
|
||||
from . import hr_employee_public
|
||||
from . import hr_department
|
||||
from . import hr_job
|
||||
from . import hr_plan
|
||||
from . import mail_alias
|
||||
from . import mail_channel
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
from . import hr_plan
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
_name = "hr.department"
|
||||
_description = "HR Department"
|
||||
_inherit = ['mail.thread']
|
||||
_order = "name"
|
||||
_rec_name = 'complete_name'
|
||||
|
||||
name = fields.Char('Department Name', required=True)
|
||||
complete_name = fields.Char('Complete Name', compute='_compute_complete_name', store=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', index=True, default=lambda self: self.env.company)
|
||||
parent_id = fields.Many2one('hr.department', string='Parent Department', index=True)
|
||||
child_ids = fields.One2many('hr.department', 'parent_id', string='Child Departments')
|
||||
manager_id = fields.Many2one('hr.employee', string='Manager', tracking=True)
|
||||
member_ids = fields.One2many('hr.employee', 'department_id', string='Members', readonly=True)
|
||||
jobs_ids = fields.One2many('hr.job', 'department_id', string='Jobs')
|
||||
note = fields.Text('Note')
|
||||
color = fields.Integer('Color Index')
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
if not self.env.context.get('hierarchical_naming', True):
|
||||
return [(record.id, record.name) for record in self]
|
||||
return super(Department, self).name_get()
|
||||
|
||||
@api.depends('name', 'parent_id.complete_name')
|
||||
def _compute_complete_name(self):
|
||||
for department in self:
|
||||
if department.parent_id:
|
||||
department.complete_name = '%s / %s' % (department.parent_id.complete_name, department.name)
|
||||
else:
|
||||
department.complete_name = department.name
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('You cannot create recursive departments.'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# TDE note: auto-subscription of manager done by hand, because currently
|
||||
# the tracking allows to track+subscribe fields linked to a res.user record
|
||||
# An update of the limited behavior should come, but not currently done.
|
||||
department = super(Department, self.with_context(mail_create_nosubscribe=True)).create(vals)
|
||||
manager = self.env['hr.employee'].browse(vals.get("manager_id"))
|
||||
if manager.user_id:
|
||||
department.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
|
||||
return department
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
""" If updating manager of a department, we need to update all the employees
|
||||
of department hierarchy, and subscribe the new manager.
|
||||
"""
|
||||
# TDE note: auto-subscription of manager done by hand, because currently
|
||||
# the tracking allows to track+subscribe fields linked to a res.user record
|
||||
# An update of the limited behavior should come, but not currently done.
|
||||
if 'manager_id' in vals:
|
||||
manager_id = vals.get("manager_id")
|
||||
if manager_id:
|
||||
manager = self.env['hr.employee'].browse(manager_id)
|
||||
# subscribe the manager user
|
||||
if manager.user_id:
|
||||
self.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
|
||||
# set the employees's parent to the new manager
|
||||
self._update_employee_manager(manager_id)
|
||||
return super(Department, self).write(vals)
|
||||
|
||||
def _update_employee_manager(self, manager_id):
|
||||
employees = self.env['hr.employee']
|
||||
for department in self:
|
||||
employees = employees | self.env['hr.employee'].search([
|
||||
('id', '!=', manager_id),
|
||||
('department_id', '=', department.id),
|
||||
('parent_id', '=', department.manager_id.id)
|
||||
])
|
||||
employees.write({'parent_id': manager_id})
|
||||
@@ -1,106 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
from random import choice
|
||||
from string import digits
|
||||
import base64
|
||||
import logging
|
||||
from werkzeug import url_encode
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID
|
||||
from odoo import tools, _
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo.exceptions import ValidationError, AccessError
|
||||
from odoo.modules.module import get_module_resource
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmployeeCategory(models.Model):
|
||||
|
||||
_name = "hr.employee.category"
|
||||
_description = "Employee Category"
|
||||
|
||||
name = fields.Char(string="Employee Tag", required=True)
|
||||
color = fields.Integer(string='Color Index')
|
||||
employee_ids = fields.Many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', string='Employees')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||
]
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
|
||||
_name = "hr.job"
|
||||
_description = "Job Position"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(string='Job Position', required=True, index=True, translate=True)
|
||||
expected_employees = fields.Integer(compute='_compute_employees', string='Total Forecasted Employees', store=True,
|
||||
help='Expected number of employees for this job position after new recruitment.')
|
||||
no_of_employee = fields.Integer(compute='_compute_employees', string="Current Number of Employees", store=True,
|
||||
help='Number of employees currently occupying this job position.')
|
||||
no_of_recruitment = fields.Integer(string='Expected New Employees', copy=False,
|
||||
help='Number of new employees you expect to recruit.', default=1)
|
||||
no_of_hired_employee = fields.Integer(string='Hired Employees', copy=False,
|
||||
help='Number of hired employees for this job position during recruitment phase.')
|
||||
employee_ids = fields.One2many('hr.employee', 'job_id', string='Employees', groups='base.group_user')
|
||||
description = fields.Text(string='Job Description')
|
||||
requirements = fields.Text('Requirements')
|
||||
department_id = fields.Many2one('hr.department', string='Department')
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
state = fields.Selection([
|
||||
('recruit', 'Recruitment in Progress'),
|
||||
('open', 'Not Recruiting')
|
||||
], string='Status', readonly=True, required=True, tracking=True, copy=False, default='recruit', help="Set whether the recruitment process is open or closed for this job position.")
|
||||
|
||||
_sql_constraints = [
|
||||
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
|
||||
]
|
||||
|
||||
@api.depends('no_of_recruitment', 'employee_ids.job_id', 'employee_ids.active')
|
||||
def _compute_employees(self):
|
||||
employee_data = self.env['hr.employee'].read_group([('job_id', 'in', self.ids)], ['job_id'], ['job_id'])
|
||||
result = dict((data['job_id'][0], data['job_id_count']) for data in employee_data)
|
||||
for job in self:
|
||||
job.no_of_employee = result.get(job.id, 0)
|
||||
job.expected_employees = result.get(job.id, 0) + job.no_of_recruitment
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
""" We don't want the current user to be follower of all created job """
|
||||
return super(Job, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {})
|
||||
if 'name' not in default:
|
||||
default['name'] = _("%s (copy)") % (self.name)
|
||||
return super(Job, self).copy(default=default)
|
||||
|
||||
@api.multi
|
||||
def set_recruit(self):
|
||||
for record in self:
|
||||
no_of_recruitment = 1 if record.no_of_recruitment == 0 else record.no_of_recruitment
|
||||
record.write({'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def set_open(self):
|
||||
return self.write({
|
||||
'state': 'open',
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0
|
||||
})
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
class HrEmployeePrivate(models.Model):
|
||||
"""
|
||||
NB: Any field only available on the model hr.employee (i.e. not on the
|
||||
hr.employee.public model) should have `groups="hr.group_hr_user"` on its
|
||||
definition to avoid being prefetched when the user hasn't access to the
|
||||
hr.employee model. Indeed, the prefetch loads the data for all the fields
|
||||
that are available according to the group defined on them.
|
||||
"""
|
||||
_name = "hr.employee"
|
||||
_description = "Employee"
|
||||
_order = 'name'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'resource.mixin']
|
||||
|
||||
_inherit = ['hr.employee.base', 'mail.thread', 'mail.activity.mixin', 'resource.mixin']
|
||||
_mail_post_access = 'read'
|
||||
|
||||
@api.model
|
||||
@@ -167,7 +89,6 @@ class Employee(models.Model):
|
||||
emergency_phone = fields.Char("Emergency Phone", groups="hr.group_hr_user", tracking=True)
|
||||
km_home_work = fields.Integer(string="Km home-work", groups="hr.group_hr_user", tracking=True)
|
||||
google_drive_link = fields.Char(string="Employee Documents", groups="hr.group_hr_user", tracking=True)
|
||||
job_title = fields.Char("Job Title")
|
||||
|
||||
# image: all image fields are base64 encoded and PIL-supported
|
||||
image = fields.Binary(
|
||||
@@ -183,17 +104,8 @@ class Employee(models.Model):
|
||||
help="Small-sized photo of the employee. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
# work
|
||||
address_id = fields.Many2one(
|
||||
'res.partner', 'Work Address')
|
||||
work_phone = fields.Char('Work Phone')
|
||||
mobile_phone = fields.Char('Work Mobile')
|
||||
phone = fields.Char(related='address_home_id.phone', related_sudo=False, string="Private Phone", groups="hr.group_hr_user")
|
||||
work_email = fields.Char('Work Email')
|
||||
work_location = fields.Char('Work Location')
|
||||
# employee in company
|
||||
job_id = fields.Many2one('hr.job', 'Job Position')
|
||||
department_id = fields.Many2one('hr.department', 'Department')
|
||||
parent_id = fields.Many2one('hr.employee', 'Manager')
|
||||
child_ids = fields.One2many('hr.employee', 'parent_id', string='Direct subordinates')
|
||||
coach_id = fields.Many2one('hr.employee', 'Coach')
|
||||
@@ -202,22 +114,80 @@ class Employee(models.Model):
|
||||
'emp_id', 'category_id',
|
||||
string='Tags')
|
||||
# misc
|
||||
notes = fields.Text('Notes')
|
||||
color = fields.Integer('Color Index', default=0)
|
||||
barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", copy=False)
|
||||
pin = fields.Char(string="PIN", help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False)
|
||||
notes = fields.Text('Notes', groups="hr.group_hr_user")
|
||||
color = fields.Integer('Color Index', default=0, groups="hr.group_hr_user")
|
||||
barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", groups="hr.group_hr_user", copy=False)
|
||||
pin = fields.Char(string="PIN", groups="hr.group_hr_user", copy=False,
|
||||
help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).")
|
||||
departure_reason = fields.Selection([
|
||||
('fired', 'Fired'),
|
||||
('resigned', 'Resigned'),
|
||||
('retired', 'Retired')
|
||||
], string="Departure Reason", copy=False, tracking=True)
|
||||
departure_description = fields.Text(string="Additional Information", copy=False, tracking=True)
|
||||
], string="Departure Reason", groups="hr.group_hr_user", copy=False, tracking=True)
|
||||
departure_description = fields.Text(string="Additional Information", groups="hr.group_hr_user", copy=False, tracking=True)
|
||||
message_main_attachment_id = fields.Many2one(groups="hr.group_hr_user")
|
||||
|
||||
_sql_constraints = [
|
||||
('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee."),
|
||||
('user_uniq', 'unique (user_id, company_id)', "A user cannot be linked to multiple employees in the same company.")
|
||||
]
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
if self.check_access_rights('read', raise_exception=False):
|
||||
return super(HrEmployeePrivate, self).name_get()
|
||||
return self.env['hr.employee.public'].browse(self.ids).name_get()
|
||||
|
||||
@api.multi
|
||||
def read(self, fields, load='_classic_read'):
|
||||
if self.check_access_rights('read', raise_exception=False):
|
||||
return super(HrEmployeePrivate, self).read(fields, load=load)
|
||||
private_fields = set(fields).difference(self.env['hr.employee.public']._fields.keys())
|
||||
if private_fields:
|
||||
raise AccessError(_('The fields "%s" you try to read is not available on the public employee profile.') % (','.join(private_fields)))
|
||||
return self.env['hr.employee.public'].browse(self.ids).read(fields, load=load)
|
||||
|
||||
@api.model
|
||||
def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None):
|
||||
"""
|
||||
We override the _search because it is the method that checks the access rights
|
||||
This is correct to override the _search. That way we enforce the fact that calling
|
||||
search on an hr.employee returns a hr.employee recordset, even if you don't have access
|
||||
to this model, as the result of _search (the ids of the public employees) is to be
|
||||
browsed on the hr.employee model. This can be trusted as the ids of the public
|
||||
employees exactly match the ids of the related hr.employee.
|
||||
"""
|
||||
if self.check_access_rights('read', raise_exception=False):
|
||||
return super(HrEmployeePrivate, self)._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
|
||||
return self.env['hr.employee.public']._search(args, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid)
|
||||
|
||||
@api.multi
|
||||
def get_formview_id(self, access_uid=None):
|
||||
""" Override this method in order to redirect many2one towards the right model depending on access_uid """
|
||||
if access_uid:
|
||||
self_sudo = self.sudo(access_uid)
|
||||
else:
|
||||
self_sudo = self
|
||||
|
||||
if self_sudo.check_access_rights('read', raise_exception=False):
|
||||
return super(HrEmployeePrivate, self).get_formview_id(access_uid=access_uid)
|
||||
# Hardcode the form view for public employee
|
||||
return self.env.ref('hr.hr_employee_public_view_form').id
|
||||
|
||||
@api.multi
|
||||
def get_formview_action(self, access_uid=None):
|
||||
""" Override this method in order to redirect many2one towards the right model depending on access_uid """
|
||||
res = super(HrEmployeePrivate, self).get_formview_action(access_uid=access_uid)
|
||||
if access_uid:
|
||||
self_sudo = self.sudo(access_uid)
|
||||
else:
|
||||
self_sudo = self
|
||||
|
||||
if not self_sudo.check_access_rights('read', raise_exception=False):
|
||||
res['res_model'] = 'hr.employee.public'
|
||||
|
||||
return res
|
||||
|
||||
@api.constrains('pin')
|
||||
def _verify_pin(self):
|
||||
for employee in self:
|
||||
@@ -278,7 +248,7 @@ class Employee(models.Model):
|
||||
vals.update(self._sync_user(user))
|
||||
vals['name'] = vals.get('name', user.name)
|
||||
tools.image_resize_images(vals)
|
||||
employee = super(Employee, self).create(vals)
|
||||
employee = super(HrEmployeePrivate, self).create(vals)
|
||||
url = '/web#%s' % url_encode({'action': 'hr.plan_wizard_action', 'active_id': employee.id, 'active_model': 'hr.employee'})
|
||||
employee._message_log(body=_('<b>Congratulations !</b> May I recommand you to setup an <a href="%s">onboarding plan ?</a>') % (url))
|
||||
if employee.department_id:
|
||||
@@ -296,7 +266,7 @@ class Employee(models.Model):
|
||||
if vals.get('user_id'):
|
||||
vals.update(self._sync_user(self.env['res.users'].browse(vals['user_id'])))
|
||||
tools.image_resize_images(vals)
|
||||
res = super(Employee, self).write(vals)
|
||||
res = super(HrEmployeePrivate, self).write(vals)
|
||||
if vals.get('department_id') or vals.get('user_id'):
|
||||
department_id = vals['department_id'] if vals.get('department_id') else self[:1].department_id.id
|
||||
# When added to a department or changing user, subscribe to the channels auto-subscribed by department
|
||||
@@ -308,11 +278,11 @@ class Employee(models.Model):
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
resources = self.mapped('resource_id')
|
||||
super(Employee, self).unlink()
|
||||
super(HrEmployeePrivate, self).unlink()
|
||||
return resources.unlink()
|
||||
|
||||
def toggle_active(self):
|
||||
res = super(Employee, self).toggle_active()
|
||||
res = super(HrEmployeePrivate, self).toggle_active()
|
||||
self.filtered(lambda employee: employee.active).write({
|
||||
'departure_reason': False,
|
||||
'departure_description': False,
|
||||
@@ -331,7 +301,8 @@ class Employee(models.Model):
|
||||
|
||||
@api.multi
|
||||
def generate_random_barcode(self):
|
||||
for i in self: i.barcode = "".join(choice(digits) for i in range(8))
|
||||
for employee in self:
|
||||
employee.barcode = "".join(choice(digits) for i in range(8))
|
||||
|
||||
@api.depends('address_home_id.parent_id')
|
||||
def _compute_is_address_home_a_company(self):
|
||||
@@ -364,88 +335,9 @@ class Employee(models.Model):
|
||||
return self
|
||||
|
||||
def _message_log(self, **kwargs):
|
||||
return super(Employee, self._post_author())._message_log(**kwargs)
|
||||
return super(HrEmployeePrivate, self._post_author())._message_log(**kwargs)
|
||||
|
||||
@api.multi
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, *args, **kwargs):
|
||||
return super(Employee, self._post_author()).message_post(*args, **kwargs)
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
_name = "hr.department"
|
||||
_description = "HR Department"
|
||||
_inherit = ['mail.thread']
|
||||
_order = "name"
|
||||
_rec_name = 'complete_name'
|
||||
|
||||
name = fields.Char('Department Name', required=True)
|
||||
complete_name = fields.Char('Complete Name', compute='_compute_complete_name', store=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', index=True, default=lambda self: self.env.company)
|
||||
parent_id = fields.Many2one('hr.department', string='Parent Department', index=True)
|
||||
child_ids = fields.One2many('hr.department', 'parent_id', string='Child Departments')
|
||||
manager_id = fields.Many2one('hr.employee', string='Manager', tracking=True)
|
||||
member_ids = fields.One2many('hr.employee', 'department_id', string='Members', readonly=True)
|
||||
jobs_ids = fields.One2many('hr.job', 'department_id', string='Jobs')
|
||||
note = fields.Text('Note')
|
||||
color = fields.Integer('Color Index')
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
if not self.env.context.get('hierarchical_naming', True):
|
||||
return [(record.id, record.name) for record in self]
|
||||
return super(Department, self).name_get()
|
||||
|
||||
@api.depends('name', 'parent_id.complete_name')
|
||||
def _compute_complete_name(self):
|
||||
for department in self:
|
||||
if department.parent_id:
|
||||
department.complete_name = '%s / %s' % (department.parent_id.complete_name, department.name)
|
||||
else:
|
||||
department.complete_name = department.name
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('You cannot create recursive departments.'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# TDE note: auto-subscription of manager done by hand, because currently
|
||||
# the tracking allows to track+subscribe fields linked to a res.user record
|
||||
# An update of the limited behavior should come, but not currently done.
|
||||
department = super(Department, self.with_context(mail_create_nosubscribe=True)).create(vals)
|
||||
manager = self.env['hr.employee'].browse(vals.get("manager_id"))
|
||||
if manager.user_id:
|
||||
department.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
|
||||
return department
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
""" If updating manager of a department, we need to update all the employees
|
||||
of department hierarchy, and subscribe the new manager.
|
||||
"""
|
||||
# TDE note: auto-subscription of manager done by hand, because currently
|
||||
# the tracking allows to track+subscribe fields linked to a res.user record
|
||||
# An update of the limited behavior should come, but not currently done.
|
||||
if 'manager_id' in vals:
|
||||
manager_id = vals.get("manager_id")
|
||||
if manager_id:
|
||||
manager = self.env['hr.employee'].browse(manager_id)
|
||||
# subscribe the manager user
|
||||
if manager.user_id:
|
||||
self.message_subscribe(partner_ids=manager.user_id.partner_id.ids)
|
||||
# set the employees's parent to the new manager
|
||||
self._update_employee_manager(manager_id)
|
||||
return super(Department, self).write(vals)
|
||||
|
||||
def _update_employee_manager(self, manager_id):
|
||||
employees = self.env['hr.employee']
|
||||
for department in self:
|
||||
employees = employees | self.env['hr.employee'].search([
|
||||
('id', '!=', manager_id),
|
||||
('department_id', '=', department.id),
|
||||
('parent_id', '=', department.manager_id.id)
|
||||
])
|
||||
employees.write({'parent_id': manager_id})
|
||||
return super(HrEmployeePrivate, self._post_author()).message_post(*args, **kwargs)
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_name = "hr.employee.base"
|
||||
_description = "Basic Employee"
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char()
|
||||
active = fields.Boolean("Active")
|
||||
department_id = fields.Many2one('hr.department', 'Department')
|
||||
job_id = fields.Many2one('hr.job', 'Job Position')
|
||||
job_title = fields.Char("Job Title")
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
address_id = fields.Many2one('res.partner', 'Work Address')
|
||||
work_phone = fields.Char('Work Phone')
|
||||
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')
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class EmployeeCategory(models.Model):
|
||||
|
||||
_name = "hr.employee.category"
|
||||
_description = "Employee Category"
|
||||
|
||||
name = fields.Char(string="Employee Tag", required=True)
|
||||
color = fields.Integer(string='Color Index')
|
||||
employee_ids = fields.Many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', string='Employees')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
||||
]
|
||||
@@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, tools
|
||||
|
||||
|
||||
class HrEmployeePublic(models.Model):
|
||||
_name = "hr.employee.public"
|
||||
_inherit = ["hr.employee.base"]
|
||||
_description = 'Public Employee'
|
||||
_order = 'name'
|
||||
_auto = False
|
||||
_log_access = True # Include magic fields
|
||||
|
||||
# Fields coming from hr.employee.base
|
||||
create_date = fields.Datetime(readonly=True)
|
||||
name = fields.Char(readonly=True)
|
||||
active = fields.Boolean(readonly=True)
|
||||
department_id = fields.Many2one(readonly=True)
|
||||
job_id = fields.Many2one(readonly=True)
|
||||
job_title = fields.Char(readonly=True)
|
||||
company_id = fields.Many2one(readonly=True)
|
||||
address_id = fields.Many2one(readonly=True)
|
||||
mobile_phone = fields.Char(readonly=True)
|
||||
work_phone = fields.Char(readonly=True)
|
||||
work_email = fields.Char(readonly=True)
|
||||
work_location = fields.Char(readonly=True)
|
||||
user_id = fields.Many2one(readonly=True)
|
||||
resource_id = fields.Many2one(readonly=True)
|
||||
resource_calendar_id = fields.Many2one(readonly=True)
|
||||
|
||||
# hr.employee.public specific fields
|
||||
child_ids = fields.One2many('hr.employee.public', 'parent_id', string='Direct subordinates', readonly=True)
|
||||
image = fields.Binary("Photo", compute='_compute_image', readonly=True, compute_sudo=True)
|
||||
image_medium = fields.Binary("Medium-sized photo", compute='_compute_image', readonly=True, compute_sudo=True)
|
||||
image_small = fields.Binary("Small-sized photo", compute='_compute_image', readonly=True, compute_sudo=True)
|
||||
parent_id = fields.Many2one('hr.employee.public', 'Manager', readonly=True)
|
||||
coach_id = fields.Many2one('hr.employee.public', 'Coach', readonly=True)
|
||||
|
||||
def _compute_image(self):
|
||||
for employee in self:
|
||||
# We have to be in sudo to have access to the images
|
||||
employee_id = self.sudo().env['hr.employee'].browse(employee.id)
|
||||
employee.image = employee_id.image
|
||||
employee.image_medium = employee_id.image_medium
|
||||
employee.image_small = employee_id.image_small
|
||||
|
||||
@api.model
|
||||
def _get_fields(self):
|
||||
return ','.join('emp.%s' % name for name, field in self._fields.items() if field.store and field.type not in ['many2many', 'one2many'])
|
||||
|
||||
@api.model_cr
|
||||
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 emp
|
||||
)""" % (self._table, self._get_fields()))
|
||||
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
|
||||
_name = "hr.job"
|
||||
_description = "Job Position"
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(string='Job Position', required=True, index=True, translate=True)
|
||||
expected_employees = fields.Integer(compute='_compute_employees', string='Total Forecasted Employees', store=True,
|
||||
help='Expected number of employees for this job position after new recruitment.')
|
||||
no_of_employee = fields.Integer(compute='_compute_employees', string="Current Number of Employees", store=True,
|
||||
help='Number of employees currently occupying this job position.')
|
||||
no_of_recruitment = fields.Integer(string='Expected New Employees', copy=False,
|
||||
help='Number of new employees you expect to recruit.', default=1)
|
||||
no_of_hired_employee = fields.Integer(string='Hired Employees', copy=False,
|
||||
help='Number of hired employees for this job position during recruitment phase.')
|
||||
employee_ids = fields.One2many('hr.employee', 'job_id', string='Employees', groups='base.group_user')
|
||||
description = fields.Text(string='Job Description')
|
||||
requirements = fields.Text('Requirements')
|
||||
department_id = fields.Many2one('hr.department', string='Department')
|
||||
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
|
||||
state = fields.Selection([
|
||||
('recruit', 'Recruitment in Progress'),
|
||||
('open', 'Not Recruiting')
|
||||
], string='Status', readonly=True, required=True, tracking=True, copy=False, default='recruit', help="Set whether the recruitment process is open or closed for this job position.")
|
||||
|
||||
_sql_constraints = [
|
||||
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
|
||||
]
|
||||
|
||||
@api.depends('no_of_recruitment', 'employee_ids.job_id', 'employee_ids.active')
|
||||
def _compute_employees(self):
|
||||
employee_data = self.env['hr.employee'].read_group([('job_id', 'in', self.ids)], ['job_id'], ['job_id'])
|
||||
result = dict((data['job_id'][0], data['job_id_count']) for data in employee_data)
|
||||
for job in self:
|
||||
job.no_of_employee = result.get(job.id, 0)
|
||||
job.expected_employees = result.get(job.id, 0) + job.no_of_recruitment
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
""" We don't want the current user to be follower of all created job """
|
||||
return super(Job, self.with_context(mail_create_nosubscribe=True)).create(values)
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {})
|
||||
if 'name' not in default:
|
||||
default['name'] = _("%s (copy)") % (self.name)
|
||||
return super(Job, self).copy(default=default)
|
||||
|
||||
@api.multi
|
||||
def set_recruit(self):
|
||||
for record in self:
|
||||
no_of_recruitment = 1 if record.no_of_recruitment == 0 else record.no_of_recruitment
|
||||
record.write({'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def set_open(self):
|
||||
return self.write({
|
||||
'state': 'open',
|
||||
'no_of_recruitment': 0,
|
||||
'no_of_hired_employee': 0
|
||||
})
|
||||
@@ -40,6 +40,13 @@
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_comp_rule" model="ir.rule">
|
||||
<field name="name">Employee multi company rule</field>
|
||||
<field name="model_id" ref="model_hr_employee_public"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id', 'in', company_ids)]</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_comp_rule" model="ir.rule">
|
||||
<field name="name">Job multi company rule</field>
|
||||
<field name="model_id" ref="model_hr_job"/>
|
||||
|
||||
@@ -2,7 +2,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_hr_employee_category_user,hr.employee.category.user,model_hr_employee_category,group_hr_user,1,1,1,1
|
||||
access_hr_employee_category_emp,hr.employee.category.emp,model_hr_employee_category,base.group_user,1,0,0,0
|
||||
access_hr_employee_user,hr.employee user,model_hr_employee,group_hr_user,1,1,1,1
|
||||
access_hr_employee_system_user,hr.employee system user,model_hr_employee,base.group_user,1,0,0,0
|
||||
access_hr_employee_system_user,hr.employee system user,model_hr_employee,base.group_user,0,0,0,0
|
||||
access_hr_employee_public_user,hr.employee_public,model_hr_employee_public,base.group_user,1,0,0,0
|
||||
access_hr_employee_resource_user,resource.resource.user,resource.model_resource_resource,group_hr_user,1,1,1,1
|
||||
access_hr_department_user,hr.department.user,model_hr_department,group_hr_user,1,1,1,1
|
||||
access_hr_department_employee,hr.department.employee,model_hr_department,base.group_user,1,0,0,0
|
||||
|
||||
|
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from odoo.addons.hr.tests.common import TestHrCommon
|
||||
from odoo.tests import new_test_user
|
||||
from odoo.exceptions import AccessError
|
||||
from datetime import date
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class TestSelfAccessRights(TestHrCommon):
|
||||
@@ -26,6 +26,8 @@ class TestSelfAccessRights(TestHrCommon):
|
||||
})
|
||||
|
||||
self.protected_fields_emp = OrderedDict([(k, v) for k, v in self.env['hr.employee']._fields.items() if v.groups == 'hr.group_hr_user'])
|
||||
# Compute fields and id field are always readable by everyone
|
||||
self.read_protected_fields_emp = OrderedDict([(k, v) for k, v in self.env['hr.employee']._fields.items() if not v.compute and k != 'id'])
|
||||
self.self_protected_fields_user = OrderedDict([
|
||||
(k, v)
|
||||
for k, v in self.env['res.users']._fields.items()
|
||||
@@ -34,14 +36,12 @@ class TestSelfAccessRights(TestHrCommon):
|
||||
|
||||
# Read hr.employee #
|
||||
def testReadSelfEmployee(self):
|
||||
for f in self.protected_fields_emp:
|
||||
with self.assertRaises(AccessError):
|
||||
self.richard_emp.sudo(self.richard)[f]
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert_emp.sudo(self.richard).read(self.protected_fields_emp.keys())
|
||||
|
||||
def testReadOtherEmployee(self):
|
||||
for f in self.protected_fields_emp:
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert_emp.sudo(self.richard)[f]
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert_emp.sudo(self.richard).read(self.protected_fields_emp.keys())
|
||||
|
||||
# Write hr.employee #
|
||||
def testWriteSelfEmployee(self):
|
||||
@@ -60,10 +60,8 @@ class TestSelfAccessRights(TestHrCommon):
|
||||
self.richard.sudo(self.richard).read([f]) # should not raise
|
||||
|
||||
def testReadOtherUserEmployee(self):
|
||||
|
||||
for f in self.self_protected_fields_user:
|
||||
with self.assertRaises(AccessError, msg="Field %s should not be readable by other usrs" % f):
|
||||
self.hubert.sudo(self.richard)[f]
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert.sudo(self.richard).read(self.self_protected_fields_user)
|
||||
|
||||
# Write res.users #
|
||||
def testWriteSelfUserEmployeeSettingFalse(self):
|
||||
@@ -76,7 +74,7 @@ class TestSelfAccessRights(TestHrCommon):
|
||||
for f, v in self.self_protected_fields_user.items():
|
||||
val = None
|
||||
if v.type == 'char' or v.type == 'text':
|
||||
val = 'dummy'
|
||||
val = '0000' if f == 'pin' else 'dummy'
|
||||
if val is not None:
|
||||
self.richard.sudo(self.richard).write({f: val})
|
||||
|
||||
@@ -116,7 +114,6 @@ class TestSelfAccessRights(TestHrCommon):
|
||||
self.richard.sudo(self.richard).write({'phone': '2154545'})
|
||||
|
||||
def testWriteOtherUserEmployee(self):
|
||||
|
||||
for f in self.self_protected_fields_user:
|
||||
with self.assertRaises(AccessError):
|
||||
self.hubert.sudo(self.richard).write({f: 'dummy'})
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_department_form" model="ir.ui.view">
|
||||
<field name="name">hr.department.form</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="department">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_department_tree" model="ir.ui.view">
|
||||
<field name="name">hr.department.tree</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Companies">
|
||||
<field name="display_name"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="parent_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_department_filter" model="ir.ui.view">
|
||||
<field name="name">hr.department.search</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Departments">
|
||||
<field name="name" string="Department"/>
|
||||
<field name="manager_id" />
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_department_view_kanban" model="ir.ui.view" >
|
||||
<field name="name">hr.department.kanban</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey o_kanban_dashboard o_hr_kanban">
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="color"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="#{kanban_color(record.color.raw_value)}">
|
||||
<div t-attf-class="o_kanban_card_header">
|
||||
<div class="o_kanban_card_header_title">
|
||||
<div class="o_primary"><field name="name"/></div>
|
||||
<div class="o_secondary"><field name="company_id" groups="base.group_multi_company"/></div>
|
||||
</div>
|
||||
<div class="o_kanban_manage_button_section">
|
||||
<a class="o_kanban_manage_toggle_button" href="#"><i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container o_kanban_card_content">
|
||||
<div class="row">
|
||||
<div class="col-6 o_kanban_primary_left">
|
||||
<button class="btn btn-primary" name="%(act_employee_from_department)d" type="action">Employees</button>
|
||||
</div>
|
||||
<div class="col-6 o_kanban_primary_right">
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
|
||||
<div class="row">
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_to_do">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>To Do</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_to_approve">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>To Approve</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_reports">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>Reporting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row">
|
||||
<div role="menuitem" aria-haspopup="true" class="col-8">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
<a role="menuitem" type="edit">Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_module_tree_department" model="ir.actions.act_window">
|
||||
<field name="name">Departments</field>
|
||||
<field name="res_model">hr.department</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="search_view_id" ref="view_department_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new department
|
||||
</p><p>
|
||||
Odoo's department structure is used to manage all documents
|
||||
related to employees by departments: expenses, timesheets,
|
||||
leaves, recruitments, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_employee_category_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.category.form</field>
|
||||
<field name="model">hr.employee.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee Tags">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_category_list" model="ir.ui.view">
|
||||
<field name="name">hr.employee.category.list</field>
|
||||
<field name="model">hr.employee.category</field>
|
||||
<field eval="8" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees Tags">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_categ_form" model="ir.actions.act_window">
|
||||
<field name="name">Employee Tags</field>
|
||||
<field name="res_model">hr.employee.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="hr_employee_public_view_search" model="ir.ui.view">
|
||||
<field name="name">hr.employee.search</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Employees">
|
||||
<field name="name" string="Employees" filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.form</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee" create="0" write="0">
|
||||
<sheet>
|
||||
<field name="user_id" invisible="1"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<!-- Used by other modules-->
|
||||
</div>
|
||||
<field name="image" widget='image' class="oe_avatar" options='{"preview_image":"image_medium"}'/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Employee's Name" required="True"/>
|
||||
</h1>
|
||||
<h2>
|
||||
<field name="job_title" placeholder="Job Title" />
|
||||
</h2>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="mobile_phone" widget="phone"/>
|
||||
<field name="work_phone" widget="phone"/>
|
||||
<field name="work_email" widget="email"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="department_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="public" string="Work Information">
|
||||
<div id="o_work_employee_container"> <!-- These two div are used to position org_chart -->
|
||||
<div id="o_work_employee_main">
|
||||
<group string="Location">
|
||||
<field name="address_id"
|
||||
context="{'show_address': 1}"
|
||||
options='{"always_reload": True, "highlight_first_line": True}'/>
|
||||
<field name="work_location"/>
|
||||
</group>
|
||||
<group name="managers" string="Managers">
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
<group string="Schedule" groups="base.group_no_one">
|
||||
<field name="resource_calendar_id"/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees">
|
||||
<field name="name"/>
|
||||
<field name="work_phone"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_view_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.employee.kanban</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_hr_employee_kanban">
|
||||
<field name="id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record">
|
||||
<!-- Medium employee's picture -->
|
||||
<div class="o_kanban_image_fill_left d-none d-md-block" t-attf-style="background-image: url(#{kanban_image('hr.employee.public', 'image_medium', record.id.raw_value)})" role="img"/>
|
||||
<!-- Employee's picture optimized for mobile -->
|
||||
<div class="o_kanban_image d-md-none rounded-circle" t-attf-style="background-image: url(#{kanban_image('hr.employee.public', 'image_small', record.id.raw_value)})" role="img"/>
|
||||
|
||||
<div class="oe_kanban_details">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="name"/>
|
||||
</strong>
|
||||
<span t-if="record.job_id.raw_value" class="o_kanban_record_subtitle"><field name="job_id"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li id="last_login"/>
|
||||
<li t-if="record.work_location.raw_value"><field name="work_location"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
<searchpanel>
|
||||
<field name="company_id" groups="base.group_multi_company" icon="fa-building"/>
|
||||
<field name="department_id" icon="fa-users"/>
|
||||
</searchpanel>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_action" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee.public</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="hr_employee_public_view_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new employee
|
||||
</p><p>
|
||||
With just a quick glance on the Odoo employee screen, you
|
||||
can easily find all the information you need for each person;
|
||||
contact data, job position, availability, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,360 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_employee_filter" model="ir.ui.view">
|
||||
<field name="name">hr.employee.search</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Employees">
|
||||
<field name="name" string="Employees" filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
|
||||
<field name="department_id" operator="child_of"/>
|
||||
<field name="category_ids" groups="hr.group_hr_user"/>
|
||||
<field name="job_id" />
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Late Activities" name="activities_overdue"
|
||||
domain="[('activity_ids.date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Today Activities" name="activities_today"
|
||||
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Future Activities" name="activities_upcoming_all"
|
||||
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_manager" string="Manager" domain="[]" context="{'group_by':'parent_id'}"/>
|
||||
<filter name="group_coach" string="Coach" domain="[]" context="{'group_by':'coach_id'}"/>
|
||||
<filter name="group_department" string="Department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter name="group_job" string="Job" domain="[]" context="{'group_by':'job_id'}"/>
|
||||
<filter name="group_company" string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee" js_class="hr_employee_form">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_partner_id" invisible="1"/>
|
||||
<header>
|
||||
<button string="Chat" class="btn btn-primary o_employee_chat_btn" attrs="{'invisible': [('user_id','=', False)]}"/>
|
||||
<button name="%(plan_wizard_action)d" string="Launch Plan" type="action" groups="hr.group_hr_manager"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<!-- Used by other modules-->
|
||||
</div>
|
||||
<field name="image" widget='image' class="oe_avatar" options='{"preview_image":"image_medium"}'/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Employee's Name" required="True"/>
|
||||
</h1>
|
||||
<h2>
|
||||
<field name="job_title" placeholder="Job Title" />
|
||||
</h2>
|
||||
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}" placeholder="Tags" groups="hr.group_hr_manager"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="mobile_phone" widget="phone"/>
|
||||
<field name="work_phone" widget="phone"/>
|
||||
<field name="work_email" widget="email"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="department_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="public" string="Work Information">
|
||||
<div id="o_work_employee_container"> <!-- These two div are used to position org_chart -->
|
||||
<div id="o_work_employee_main">
|
||||
<group string="Location">
|
||||
<field name="address_id"
|
||||
context="{'show_address': 1}"
|
||||
options='{"always_reload": True, "highlight_first_line": True}'/>
|
||||
<field name="work_location"/>
|
||||
</group>
|
||||
<group name="managers" string="Managers">
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
<group name="departure" string="Departure" attrs="{'invisible': [('active', '=', True)]}">
|
||||
<field name="departure_reason"/>
|
||||
<field name="departure_description"/>
|
||||
</group>
|
||||
<group string="Schedule" groups="base.group_no_one">
|
||||
<field name="resource_calendar_id" required="1"/>
|
||||
<field name="tz"/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</page>
|
||||
<page name="personal_information" string="Private Information" groups="hr.group_hr_user">
|
||||
<group>
|
||||
<group string="Contact Information">
|
||||
<field name="address_home_id"
|
||||
context="{
|
||||
'show_address': 1,
|
||||
'default_type': 'private',
|
||||
'form_view_ref': 'base.res_partner_view_form_private'}"
|
||||
options='{"always_reload": True, "highlight_first_line": True}'/>
|
||||
<field name="is_address_home_a_company" invisible="1"/>
|
||||
<div class="text-warning" groups="hr.group_hr_user"
|
||||
attrs="{'invisible': [('is_address_home_a_company','=', False)]}">
|
||||
Use here the home address of the employee.
|
||||
This private address is used in the expense report reimbursement document.
|
||||
It should be different from the work address.
|
||||
</div>
|
||||
<field name="km_home_work" groups="hr.group_hr_user"/>
|
||||
<field name="phone" groups="hr.group_hr_user"/>
|
||||
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
|
||||
</group>
|
||||
<group string="Citizenship">
|
||||
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
|
||||
<field name="identification_id" groups="hr.group_hr_user"/>
|
||||
<field name="passport_id" groups="hr.group_hr_user"/>
|
||||
<field name="gender"/>
|
||||
<field name="birthday"/>
|
||||
<field name="place_of_birth" groups="hr.group_hr_user"/>
|
||||
<field name="country_of_birth" groups="hr.group_hr_user"/>
|
||||
</group>
|
||||
<group string="Marital Status">
|
||||
<field name="marital"/>
|
||||
<field name="spouse_complete_name" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
|
||||
<field name="spouse_birthdate" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
|
||||
</group>
|
||||
<group string="Education">
|
||||
<field name="certificate"/>
|
||||
<field name="study_field"/>
|
||||
<field name="study_school"/>
|
||||
</group>
|
||||
<group string="Dependant">
|
||||
<field name="children"/>
|
||||
</group>
|
||||
<group string="Emergency">
|
||||
<field name="emergency_contact"/>
|
||||
<field name="emergency_phone"/>
|
||||
</group>
|
||||
<group string="Work Permit" name="work_permit">
|
||||
<field name="visa_no"/>
|
||||
<field name="permit_no"/>
|
||||
<field name="visa_expire"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="google_drive_link" widget="url"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="hr_settings" string="HR Settings" groups="hr.group_hr_user">
|
||||
<group>
|
||||
<group string='Status' name="active_group">
|
||||
<field name="user_id" string="Related User"/>
|
||||
</group>
|
||||
<group string="Login Information" name="identification_group">
|
||||
<field name="pin"/>
|
||||
<field name="barcode"/>
|
||||
<button string="Generate Badge ID" class="btn btn-primary" type="object" name="generate_random_barcode" attrs="{'invisible': [('barcode', '!=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
|
||||
<field name="activity_ids" widget="mail_activity"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_tree" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees">
|
||||
<field name="name"/>
|
||||
<field name="work_phone"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_kanban_view_employees" model="ir.ui.view">
|
||||
<field name="name">hr.employee.kanban</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_hr_employee_kanban">
|
||||
<field name="id"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record">
|
||||
<!-- Medium employee's picture -->
|
||||
<div class="o_kanban_image_fill_left d-none d-md-block" t-attf-style="background-image: url(#{kanban_image('hr.employee', 'image_medium', record.id.raw_value)})" role="img"/>
|
||||
<!-- Employee's picture optimized for mobile -->
|
||||
<div class="o_kanban_image d-md-none rounded-circle" t-attf-style="background-image: url(#{kanban_image('hr.employee', 'image_small', record.id.raw_value)})" role="img"/>
|
||||
|
||||
<div class="oe_kanban_details">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="name"/>
|
||||
</strong>
|
||||
<span t-if="record.job_id.raw_value" class="o_kanban_record_subtitle"><field name="job_id"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<ul>
|
||||
<li id="last_login"/>
|
||||
<li t-if="record.work_location.raw_value"><field name="work_location"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
<searchpanel>
|
||||
<field name="company_id" groups="base.group_multi_company" icon="fa-building"/>
|
||||
<field name="department_id" icon="fa-users"/>
|
||||
<field name="category_ids" select="multi" icon="fa-tags"/>
|
||||
</searchpanel>
|
||||
</kanban>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_view_activity" model="ir.ui.view">
|
||||
<field name="name">hr.employee.activity</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<activity string="Employees">
|
||||
<field name="id"/>
|
||||
<templates>
|
||||
<div t-name="activity-box">
|
||||
<img t-att-src="activity_image('hr.employee', 'image_small', record.id.raw_value)" role="img" t-att-title="record.id.value" t-att-alt="record.id.value"/>
|
||||
<div>
|
||||
<field name="name" display="full"/>
|
||||
<field name="job_id" muted="1" display="full"/>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
</activity>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_list_my" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form,activity</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new employee
|
||||
</p><p>
|
||||
With just a quick glance on the Odoo employee screen, you
|
||||
can easily find all the information you need for each person;
|
||||
contact data, job position, availability, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_tree" model="ir.actions.act_window">
|
||||
<field name="name">Employees Structure</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_employee_tree"/>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_list" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
</record>
|
||||
|
||||
<!-- Employee tree by manager -->
|
||||
<record id="view_partner_tree2" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees">
|
||||
<field name="name"/>
|
||||
<field name="work_phone"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="coach_id" invisible="1"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TODO remove me in master -->
|
||||
<record id="hr_employee_action_subordinate_hierachy" model="ir.actions.act_window">
|
||||
<field name="name">Subordinate Hierarchy</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="domain">['|', ('id','in',active_ids), ('parent_id', 'in', active_ids)]</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_partner_tree2"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.server" id="hr_employee_action_toggle_active">
|
||||
<field name="name">Archive/Restore</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
if records:
|
||||
records.toggle_active()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_employee_from_department" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,form,tree</field>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
<field name="context">{
|
||||
"search_default_department_id": [active_id],
|
||||
"default_department_id": active_id}
|
||||
</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new employee
|
||||
</p><p>
|
||||
With just a quick glance on the Odoo employee screen, you
|
||||
can easily find all the information you need for each person;
|
||||
contact data, job position, availability, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="view_hr_job_form" model="ir.ui.view">
|
||||
<field name="name">hr.job.form</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job">
|
||||
<header>
|
||||
<button name="set_recruit" string="Start Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" groups="base.group_user"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="e.g. Sales Manager"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="recruitment">
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
</group>
|
||||
</group>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</div>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_tree" model="ir.ui.view">
|
||||
<field name="name">hr.job.tree</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Job" decoration-bf="message_needaction==True">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_employee"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="expected_employees"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="state"/>
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_view_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.job.kanban</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div>
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span><field name="department_id"/>&nbsp;</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Vacancies : <field name="expected_employees"/></span>
|
||||
<span t-att-class="record.state.raw_value == 'recruit' and 'float-right badge badge-success' or 'float-right badge badge-danger'">
|
||||
<field name="state"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_job_filter" model="ir.ui.view">
|
||||
<field name="name">hr.job.search</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Jobs">
|
||||
<field name="name" string="Job"/>
|
||||
<filter domain="[('state','=','open')]" name="in_position" string="In Position"/>
|
||||
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
|
||||
<field name="department_id" operator="child_of"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Department" name="department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" name="status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" name="company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_job" model="ir.actions.act_window">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_job_filter"/>
|
||||
<field name="context">{"search_default_Current":1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Ready to recruit more efficiently?
|
||||
</p><p>
|
||||
Let's create a job position.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="hr_plan_view_search" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.search</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Plan">
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.tree</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Planning">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.form</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Planning">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group string="Activities">
|
||||
<field name="plan_activity_type_ids" nolabel="1">
|
||||
<tree editable="bottom">
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
<field name="responsible_id" attrs="{'readonly': [('responsible', '!=', 'other')]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_activity_type_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.plan.activity.type.view.tree</field>
|
||||
<field name="model">hr.plan.activity.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Activities">
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_activity_type_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.plan.activity.type.view.form</field>
|
||||
<field name="model">hr.plan.activity.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Activity">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
<field name="responsible_id" attrs="{'invisible': [('responsible', '!=', 'other')]}"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_action" model="ir.actions.act_window">
|
||||
<field name="name">Planning</field>
|
||||
<field name="res_model">hr.plan</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="hr_plan_view_search"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_activity_type_action" model="ir.actions.act_window">
|
||||
<field name="name">Planning Types</field>
|
||||
<field name="res_model">hr.plan.activity.type</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
+65
-807
@@ -2,7 +2,6 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- Top menu item -->
|
||||
<menuitem
|
||||
id="menu_hr_root"
|
||||
name="Employees"
|
||||
@@ -16,6 +15,27 @@
|
||||
parent="menu_hr_root"
|
||||
sequence="0"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_hr_employee_payroll"
|
||||
name="Payroll"
|
||||
parent="menu_hr_root"
|
||||
groups="group_hr_user"
|
||||
sequence="3"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_hr_employee_user"
|
||||
name="Employees"
|
||||
action="open_view_employee_list_my"
|
||||
parent="menu_hr_employee_payroll"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_hr_employee"
|
||||
name="Company Address"
|
||||
action="hr_employee_public_action"
|
||||
parent="menu_hr_root"
|
||||
sequence="4"/>
|
||||
|
||||
<menuitem
|
||||
id="hr_menu_hr_reports"
|
||||
name="Reporting"
|
||||
@@ -23,11 +43,11 @@
|
||||
sequence="95"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_hr_reporting_timesheet"
|
||||
name="Reporting"
|
||||
parent="menu_hr_root"
|
||||
groups="group_hr_manager,group_hr_user"
|
||||
sequence="99"/>
|
||||
id="menu_hr_reporting_timesheet"
|
||||
name="Reporting"
|
||||
parent="menu_hr_root"
|
||||
groups="group_hr_manager,group_hr_user"
|
||||
sequence="99"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_human_resources_configuration"
|
||||
@@ -36,811 +56,49 @@
|
||||
groups="group_hr_manager"
|
||||
sequence="100"/>
|
||||
|
||||
<record id="hr_plan_view_search" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.search</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Plan">
|
||||
<field name="name"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_human_resources_configuration_employee"
|
||||
name="Employee"
|
||||
parent="menu_human_resources_configuration"
|
||||
groups="base.group_no_one"
|
||||
sequence="1"/>
|
||||
|
||||
<record id="hr_plan_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.tree</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Planning">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_view_employee_category_form"
|
||||
name="Tags"
|
||||
action="open_view_categ_form"
|
||||
parent="menu_human_resources_configuration_employee"
|
||||
groups="base.group_no_one"
|
||||
sequence="1"/>
|
||||
|
||||
<record id="hr_plan_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.plan.view.form</field>
|
||||
<field name="model">hr.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Planning">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object"
|
||||
class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group string="Activities">
|
||||
<field name="plan_activity_type_ids" nolabel="1">
|
||||
<tree editable="bottom">
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
<field name="responsible_id" attrs="{'readonly': [('responsible', '!=', 'other')]}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_hr_department_tree"
|
||||
action="open_module_tree_department"
|
||||
parent="menu_human_resources_configuration"
|
||||
sequence="2"
|
||||
groups="group_hr_user"/>
|
||||
|
||||
<record id="hr_plan_activity_type_view_tree" model="ir.ui.view">
|
||||
<field name="name">hr.plan.activity.type.view.tree</field>
|
||||
<field name="model">hr.plan.activity.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Activities">
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem
|
||||
id="menu_config_plan"
|
||||
name="Activity Planning"
|
||||
parent="menu_human_resources_configuration"
|
||||
groups="group_hr_manager"
|
||||
sequence="100"/>
|
||||
|
||||
<record id="hr_plan_activity_type_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.plan.activity.type.view.form</field>
|
||||
<field name="model">hr.plan.activity.type</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Activity">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="activity_type_id"/>
|
||||
<field name="summary"/>
|
||||
<field name="responsible"/>
|
||||
<field name="responsible_id" attrs="{'invisible': [('responsible', '!=', 'other')]}"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_action" model="ir.actions.act_window">
|
||||
<field name="name">Planning</field>
|
||||
<field name="res_model">hr.plan</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="hr_plan_view_search"/>
|
||||
</record>
|
||||
|
||||
<record id="hr_plan_activity_type_action" model="ir.actions.act_window">
|
||||
<field name="name">Planning Types</field>
|
||||
<field name="res_model">hr.plan.activity.type</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_config_plan"
|
||||
name="Activity Planning"
|
||||
parent="menu_human_resources_configuration"
|
||||
groups="group_hr_manager"
|
||||
sequence="100"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_config_plan_types"
|
||||
name="Planning Types"
|
||||
action="hr_plan_activity_type_action"
|
||||
parent="menu_config_plan"
|
||||
groups="base.group_no_one"
|
||||
sequence="99"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_config_plan_plan"
|
||||
name="Plans"
|
||||
action="hr_plan_action"
|
||||
parent="menu_config_plan"
|
||||
groups="group_hr_manager"
|
||||
sequence="100"/>
|
||||
|
||||
<!-- Employee -->
|
||||
<record id="view_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee" js_class="hr_employee_form">
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="user_partner_id" invisible="1"/>
|
||||
<header>
|
||||
<button string="Chat" class="btn btn-primary o_employee_chat_btn" attrs="{'invisible': [('user_id','=', False)]}"/>
|
||||
<button name="%(plan_wizard_action)d" string="Launch Plan" type="action" groups="hr.group_hr_manager"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<!-- Used by other modules-->
|
||||
</div>
|
||||
<field name="image" widget='image' class="oe_avatar" options='{"preview_image":"image_medium"}'/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Employee's Name" required="True"/>
|
||||
</h1>
|
||||
<h2>
|
||||
<field name="job_title" placeholder="Job Title" />
|
||||
</h2>
|
||||
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True}" placeholder="Tags" groups="hr.group_hr_manager"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="mobile_phone" widget="phone"/>
|
||||
<field name="work_phone" widget="phone"/>
|
||||
<field name="work_email" widget="email"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="department_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="public" string="Work Information">
|
||||
<div id="o_work_employee_container"> <!-- These two div are used to position org_chart -->
|
||||
<div id="o_work_employee_main">
|
||||
<group string="Location">
|
||||
<field name="address_id"
|
||||
context="{'show_address': 1}"
|
||||
options='{"always_reload": True, "highlight_first_line": True}'/>
|
||||
<field name="work_location"/>
|
||||
</group>
|
||||
<group name="managers" string="Managers">
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id"/>
|
||||
</group>
|
||||
<group name="departure" string="Departure" attrs="{'invisible': [('active', '=', True)]}">
|
||||
<field name="departure_reason"/>
|
||||
<field name="departure_description"/>
|
||||
</group>
|
||||
<group string="Schedule" groups="base.group_no_one">
|
||||
<field name="resource_calendar_id" required="1"/>
|
||||
<field name="tz"/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</page>
|
||||
<page name="personal_information" string="Private Information" groups="hr.group_hr_user">
|
||||
<group>
|
||||
<group string="Contact Information">
|
||||
<field name="address_home_id"
|
||||
context="{
|
||||
'show_address': 1,
|
||||
'default_type': 'private',
|
||||
'form_view_ref': 'base.res_partner_view_form_private'}"
|
||||
options='{"always_reload": True, "highlight_first_line": True}'/>
|
||||
<field name="is_address_home_a_company" invisible="1"/>
|
||||
<div class="text-warning" groups="hr.group_hr_user"
|
||||
attrs="{'invisible': [('is_address_home_a_company','=', False)]}">
|
||||
Use here the home address of the employee.
|
||||
This private address is used in the expense report reimbursement document.
|
||||
It should be different from the work address.
|
||||
</div>
|
||||
<field name="km_home_work" groups="hr.group_hr_user"/>
|
||||
<field name="phone" groups="hr.group_hr_user"/>
|
||||
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
|
||||
</group>
|
||||
<group string="Citizenship">
|
||||
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
|
||||
<field name="identification_id" groups="hr.group_hr_user"/>
|
||||
<field name="passport_id" groups="hr.group_hr_user"/>
|
||||
<field name="gender"/>
|
||||
<field name="birthday"/>
|
||||
<field name="place_of_birth" groups="hr.group_hr_user"/>
|
||||
<field name="country_of_birth" groups="hr.group_hr_user"/>
|
||||
</group>
|
||||
<group string="Marital Status">
|
||||
<field name="marital"/>
|
||||
<field name="spouse_complete_name" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
|
||||
<field name="spouse_birthdate" attrs="{'invisible': [('marital', 'not in', ['married', 'cohabitant'])]}" groups="hr.group_hr_user"/>
|
||||
</group>
|
||||
<group string="Education">
|
||||
<field name="certificate"/>
|
||||
<field name="study_field"/>
|
||||
<field name="study_school"/>
|
||||
</group>
|
||||
<group string="Dependant">
|
||||
<field name="children"/>
|
||||
</group>
|
||||
<group string="Emergency">
|
||||
<field name="emergency_contact"/>
|
||||
<field name="emergency_phone"/>
|
||||
</group>
|
||||
<group string="Work Permit" name="work_permit">
|
||||
<field name="visa_no"/>
|
||||
<field name="permit_no"/>
|
||||
<field name="visa_expire"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="google_drive_link" widget="url"/>
|
||||
</group>
|
||||
</page>
|
||||
<page name="hr_settings" string="HR Settings" groups="hr.group_hr_user">
|
||||
<group>
|
||||
<group string='Status' name="active_group">
|
||||
<field name="user_id" string="Related User"/>
|
||||
</group>
|
||||
<group string="Login Information" name="identification_group">
|
||||
<field name="pin"/>
|
||||
<field name="barcode"/>
|
||||
<button string="Generate Badge ID" class="btn btn-primary" type="object" name="generate_random_barcode" attrs="{'invisible': [('barcode', '!=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
|
||||
<field name="activity_ids" widget="mail_activity"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_employee_tree" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees" decoration-bf="message_needaction==True">
|
||||
<field name="name"/>
|
||||
<field name="work_phone"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="coach_id" invisible="1"/>
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_filter" model="ir.ui.view">
|
||||
<field name="name">hr.employee.search</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Employees">
|
||||
<field name="name" string="Employees" filter_domain="['|',('work_email','ilike',self),('name','ilike',self)]"/>
|
||||
<field name="department_id" operator="child_of"/>
|
||||
<field name="category_ids" groups="hr.group_hr_user"/>
|
||||
<field name="job_id" />
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
|
||||
<separator/>
|
||||
<filter string="Late Activities" name="activities_overdue"
|
||||
domain="[('activity_ids.date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Today Activities" name="activities_today"
|
||||
domain="[('activity_ids.date_deadline', '=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Future Activities" name="activities_upcoming_all"
|
||||
domain="[('activity_ids.date_deadline', '>', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_manager" string="Manager" domain="[]" context="{'group_by':'parent_id'}"/>
|
||||
<filter name="group_coach" string="Coach" domain="[]" context="{'group_by':'coach_id'}"/>
|
||||
<filter name="group_department" string="Department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter name="group_job" string="Job" domain="[]" context="{'group_by':'job_id'}"/>
|
||||
<filter name="group_company" string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_kanban_view_employees" model="ir.ui.view">
|
||||
<field name="name">hr.employee.kanban</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_hr_employee_kanban">
|
||||
<field name="id"/>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
<field name="message_needaction_counter"/>
|
||||
<field name="activity_state"/>
|
||||
<progressbar field="activity_state" colors='{"planned": "success", "today": "warning", "overdue": "danger"}'/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click o_kanban_record_has_image_fill o_hr_kanban_record">
|
||||
<!-- Medium employee's picture -->
|
||||
<div class="o_kanban_image_fill_left d-none d-md-block" t-attf-style="background-image: url(#{kanban_image('hr.employee', 'image_medium', record.id.raw_value)})" role="img"/>
|
||||
<!-- Employee's picture optimized for mobile -->
|
||||
<div class="o_kanban_image d-md-none rounded-circle" t-attf-style="background-image: url(#{kanban_image('hr.employee', 'image_small', record.id.raw_value)})" role="img"/>
|
||||
|
||||
<div class="oe_kanban_details">
|
||||
<div class="o_kanban_record_top">
|
||||
<div class="o_kanban_record_headings">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="name"/>
|
||||
</strong>
|
||||
<span t-if="record.job_id.raw_value" class="o_kanban_record_subtitle"><field name="job_id"/></span>
|
||||
</div>
|
||||
</div>
|
||||
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<ul>
|
||||
<li id="last_login"/>
|
||||
<li t-if="record.work_location.raw_value"><field name="work_location"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
<searchpanel>
|
||||
<field name="company_id" groups="base.group_multi_company" icon="fa-building"/>
|
||||
<field name="department_id" icon="fa-users"/>
|
||||
<field name="category_ids" select="multi" icon="fa-tags"/>
|
||||
</searchpanel>
|
||||
</kanban>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_view_activity" model="ir.ui.view">
|
||||
<field name="name">hr.employee.activity</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="arch" type="xml">
|
||||
<activity string="Employees">
|
||||
<field name="id"/>
|
||||
<templates>
|
||||
<div t-name="activity-box">
|
||||
<img t-att-src="activity_image('hr.employee', 'image_small', record.id.raw_value)" role="img" t-att-title="record.id.value" t-att-alt="record.id.value"/>
|
||||
<div>
|
||||
<field name="name" display="full"/>
|
||||
<field name="job_id" muted="1" display="full"/>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
</activity>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_tree" model="ir.actions.act_window">
|
||||
<field name="name">Employees Structure</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">tree</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_employee_tree"/>
|
||||
<field name="domain">[('parent_id','=',False)]</field>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_list" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form,tree</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
</record>
|
||||
|
||||
<record id="open_view_employee_list_my" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form,activity</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="view_id" eval="False"/>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new employee
|
||||
</p><p>
|
||||
With just a quick glance on the Odoo employee screen, you
|
||||
can easily find all the information you need for each person;
|
||||
contact data, job position, availability, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_open_view_employee_list_my"
|
||||
action="open_view_employee_list_my"
|
||||
parent="menu_hr_root"
|
||||
sequence="3"/>
|
||||
|
||||
<!-- Employee tree by manager -->
|
||||
<record id="view_partner_tree2" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees">
|
||||
<field name="name"/>
|
||||
<field name="work_phone"/>
|
||||
<field name="work_email"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="job_id"/>
|
||||
<field name="coach_id" invisible="1"/>
|
||||
<field name="parent_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TODO remove me in master -->
|
||||
<record id="hr_employee_action_subordinate_hierachy" model="ir.actions.act_window">
|
||||
<field name="name">Subordinate Hierarchy</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="domain">['|', ('id','in',active_ids), ('parent_id', 'in', active_ids)]</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_partner_tree2"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
</record>
|
||||
|
||||
<!-- Employee Category -->
|
||||
<record id="view_employee_category_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.category.form</field>
|
||||
<field name="model">hr.employee.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee Tags">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_employee_category_list" model="ir.ui.view">
|
||||
<field name="name">hr.employee.category.list</field>
|
||||
<field name="model">hr.employee.category</field>
|
||||
<field eval="8" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employees Tags">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_view_categ_form" model="ir.actions.act_window">
|
||||
<field name="name">Employee Tags</field>
|
||||
<field name="res_model">hr.employee.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
id="menu_human_resources_configuration_employee"
|
||||
name="Employee"
|
||||
parent="menu_human_resources_configuration"
|
||||
groups="base.group_no_one"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_view_employee_category_form"
|
||||
name="Tags"
|
||||
action="open_view_categ_form"
|
||||
parent="menu_human_resources_configuration_employee"
|
||||
groups="base.group_no_one"
|
||||
sequence="1"/>
|
||||
|
||||
<!-- hr.job -->
|
||||
<record id="view_hr_job_form" model="ir.ui.view">
|
||||
<field name="name">hr.job.form</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Job">
|
||||
<header>
|
||||
<button name="set_recruit" string="Start Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" groups="base.group_user"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name" placeholder="e.g. Sales Manager"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="recruitment">
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
</group>
|
||||
</group>
|
||||
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
|
||||
<label for="description"/>
|
||||
<field name="description"/>
|
||||
</div>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_job_tree" model="ir.ui.view">
|
||||
<field name="name">hr.job.tree</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Job" decoration-bf="message_needaction==True">
|
||||
<field name="name"/>
|
||||
<field name="department_id"/>
|
||||
<field name="no_of_employee"/>
|
||||
<field name="no_of_recruitment"/>
|
||||
<field name="expected_employees"/>
|
||||
<field name="no_of_hired_employee"/>
|
||||
<field name="state"/>
|
||||
<field name="message_needaction" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_job_view_kanban" model="ir.ui.view">
|
||||
<field name="name">hr.job.kanban</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_mobile">
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div>
|
||||
<strong><field name="name"/></strong>
|
||||
</div>
|
||||
<div>
|
||||
<span><field name="department_id"/>&nbsp;</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Vacancies : <field name="expected_employees"/></span>
|
||||
<span t-att-class="record.state.raw_value == 'recruit' and 'float-right badge badge-success' or 'float-right badge badge-danger'">
|
||||
<field name="state"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_job_filter" model="ir.ui.view">
|
||||
<field name="name">hr.job.search</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Jobs">
|
||||
<field name="name" string="Job"/>
|
||||
<filter domain="[('state','=','open')]" name="in_position" string="In Position"/>
|
||||
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
|
||||
<field name="department_id" operator="child_of"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Department" name="department" domain="[]" context="{'group_by':'department_id'}"/>
|
||||
<filter string="Status" name="status" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" name="company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_job" model="ir.actions.act_window">
|
||||
<field name="name">Job Positions</field>
|
||||
<field name="res_model">hr.job</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="search_view_id" ref="view_job_filter"/>
|
||||
<field name="context">{"search_default_Current":1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Ready to recruit more efficiently?
|
||||
</p><p>
|
||||
Let's create a job position.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.server" id="hr_employee_action_toggle_active">
|
||||
<field name="name">Archive/Restore</field>
|
||||
<field name="model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
if records:
|
||||
records.toggle_active()
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="act_employee_from_department" model="ir.actions.act_window">
|
||||
<field name="name">Employees</field>
|
||||
<field name="res_model">hr.employee</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,form,tree</field>
|
||||
<field name="search_view_id" ref="view_employee_filter"/>
|
||||
<field name="context">{
|
||||
"search_default_department_id": [active_id],
|
||||
"default_department_id": active_id}
|
||||
</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new employee
|
||||
</p><p>
|
||||
With just a quick glance on the Odoo employee screen, you
|
||||
can easily find all the information you need for each person;
|
||||
contact data, job position, availability, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- hr.department -->
|
||||
<record id="view_department_form" model="ir.ui.view">
|
||||
<field name="name">hr.department.form</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="department">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<group col="4">
|
||||
<field name="name"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_department_tree" model="ir.ui.view">
|
||||
<field name="name">hr.department.tree</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="field_parent">child_ids</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Companies">
|
||||
<field name="display_name"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="parent_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_department_filter" model="ir.ui.view">
|
||||
<field name="name">hr.department.search</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Departments">
|
||||
<field name="name" string="Department"/>
|
||||
<field name="manager_id" />
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_department_view_kanban" model="ir.ui.view" >
|
||||
<field name="name">hr.department.kanban</field>
|
||||
<field name="model">hr.department</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey o_kanban_dashboard o_hr_kanban">
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="color"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="#{kanban_color(record.color.raw_value)}">
|
||||
<div t-attf-class="o_kanban_card_header">
|
||||
<div class="o_kanban_card_header_title">
|
||||
<div class="o_primary"><field name="name"/></div>
|
||||
<div class="o_secondary"><field name="company_id" groups="base.group_multi_company"/></div>
|
||||
</div>
|
||||
<div class="o_kanban_manage_button_section">
|
||||
<a class="o_kanban_manage_toggle_button" href="#"><i class="fa fa-ellipsis-v" role="img" aria-label="Manage" title="Manage"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container o_kanban_card_content">
|
||||
<div class="row">
|
||||
<div class="col-6 o_kanban_primary_left">
|
||||
<button class="btn btn-primary" name="%(act_employee_from_department)d" type="action">Employees</button>
|
||||
</div>
|
||||
<div class="col-6 o_kanban_primary_right">
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
|
||||
<div class="row">
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_to_do">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>To Do</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_to_approve">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>To Approve</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="menuitem" class="col-4 o_kanban_card_manage_section o_kanban_manage_reports">
|
||||
<div class="o_kanban_card_manage_title">
|
||||
<span>Reporting</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row">
|
||||
<div role="menuitem" aria-haspopup="true" class="col-8">
|
||||
<ul class="oe_kanban_colorpicker" data-field="color" role="menu"/>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
<a role="menuitem" type="edit">Settings</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_module_tree_department" model="ir.actions.act_window">
|
||||
<field name="name">Departments</field>
|
||||
<field name="res_model">hr.department</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="search_view_id" ref="view_department_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new department
|
||||
</p><p>
|
||||
Odoo's department structure is used to manage all documents
|
||||
related to employees by departments: expenses, timesheets,
|
||||
leaves, recruitments, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_users_form_simple_modif_resource" model="ir.ui.view">
|
||||
<field name="name">res.users.preferences.form.resource</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form_simple_modif" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="tz" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
action="open_module_tree_department"
|
||||
id="menu_hr_department_tree"
|
||||
parent="menu_hr_root"
|
||||
sequence="90"
|
||||
groups="group_hr_user"/>
|
||||
<menuitem
|
||||
id="menu_config_plan_types"
|
||||
name="Planning Types"
|
||||
action="hr_plan_activity_type_action"
|
||||
parent="menu_config_plan"
|
||||
groups="base.group_no_one"
|
||||
sequence="99"/>
|
||||
|
||||
<menuitem
|
||||
id="menu_config_plan_plan"
|
||||
name="Plans"
|
||||
action="hr_plan_action"
|
||||
parent="menu_config_plan"
|
||||
groups="group_hr_manager"
|
||||
sequence="100"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -18,6 +18,17 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_users_form_simple_modif_resource" model="ir.ui.view">
|
||||
<field name="name">res.users.preferences.form.resource</field>
|
||||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="base.view_users_form_simple_modif" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="tz" position="attributes">
|
||||
<attribute name="required">1</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_users_view_form_profile" model="ir.ui.view">
|
||||
<field name="name">res.users.preferences.form.inherit</field>
|
||||
<field name="model">res.users</field>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import models, fields, api, exceptions, _, SUPERUSER_ID
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import models, fields, api, exceptions, _, SUPERUSER_ID
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
_description = "Employee"
|
||||
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_inherit = "hr.employee.base"
|
||||
|
||||
attendance_ids = fields.One2many('hr.attendance', 'employee_id', help='list of attendances for the employee')
|
||||
last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id', store=True)
|
||||
@@ -44,35 +45,37 @@ class HrEmployee(models.Model):
|
||||
""" Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee.
|
||||
Returns either an action or a warning.
|
||||
"""
|
||||
employee = self.search([('barcode', '=', barcode)], limit=1)
|
||||
return employee and employee.attendance_action('hr_attendance.hr_attendance_action_kiosk_mode') or \
|
||||
{'warning': _('No employee corresponding to barcode %(barcode)s') % {'barcode': barcode}}
|
||||
employee = self.sudo().search([('barcode', '=', barcode)], limit=1)
|
||||
if employee:
|
||||
return employee._attendance_action('hr_attendance.hr_attendance_action_kiosk_mode')
|
||||
return {'warning': _('No employee corresponding to barcode %(barcode)s') % {'barcode': barcode}}
|
||||
|
||||
@api.multi
|
||||
def attendance_manual(self, next_action, entered_pin=None):
|
||||
self.ensure_one()
|
||||
if not (entered_pin is None) or self.env['res.users'].browse(SUPERUSER_ID).has_group('hr_attendance.group_hr_attendance_use_pin') and (self.user_id and self.user_id.id != self._uid or not self.user_id):
|
||||
if not (entered_pin is None) or self.env.user.has_group('hr_attendance.group_hr_attendance_use_pin') and (self.user_id and self.user_id == self.env.user):
|
||||
if entered_pin != self.pin:
|
||||
return {'warning': _('Wrong PIN')}
|
||||
return self.attendance_action(next_action)
|
||||
return self._attendance_action(next_action)
|
||||
|
||||
@api.multi
|
||||
def attendance_action(self, next_action):
|
||||
def _attendance_action(self, next_action):
|
||||
""" Changes the attendance of the employee.
|
||||
Returns an action to the check in/out message,
|
||||
next_action defines which menu the check in/out message should return to. ("My Attendances" or "Kiosk Mode")
|
||||
"""
|
||||
self.ensure_one()
|
||||
employee = self.sudo()
|
||||
action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0]
|
||||
action_message['previous_attendance_change_date'] = self.last_attendance_id and (self.last_attendance_id.check_out or self.last_attendance_id.check_in) or False
|
||||
action_message['employee_name'] = self.name
|
||||
action_message['barcode'] = self.barcode
|
||||
action_message['previous_attendance_change_date'] = employee.last_attendance_id and (employee.last_attendance_id.check_out or employee.last_attendance_id.check_in) or False
|
||||
action_message['employee_name'] = employee.name
|
||||
action_message['barcode'] = employee.barcode
|
||||
action_message['next_action'] = next_action
|
||||
|
||||
if self.user_id:
|
||||
modified_attendance = self.sudo(self.user_id.id).attendance_action_change()
|
||||
if employee.user_id:
|
||||
modified_attendance = employee.sudo(employee.user_id.id).attendance_action_change()
|
||||
else:
|
||||
modified_attendance = self.sudo().attendance_action_change()
|
||||
modified_attendance = employee.attendance_action_change()
|
||||
action_message['attendance'] = modified_attendance.read()[0]
|
||||
return {'action': action_message}
|
||||
|
||||
@@ -82,8 +85,7 @@ class HrEmployee(models.Model):
|
||||
Check In: create a new attendance record
|
||||
Check Out: modify check_out field of appropriate attendance record
|
||||
"""
|
||||
if len(self) > 1:
|
||||
raise exceptions.UserError(_('Cannot perform check in or check out on multiple employees.'))
|
||||
self.ensure_one()
|
||||
action_date = fields.Datetime.now()
|
||||
|
||||
if self.attendance_state != 'checked_in':
|
||||
@@ -92,11 +94,10 @@ class HrEmployee(models.Model):
|
||||
'check_in': action_date,
|
||||
}
|
||||
return self.env['hr.attendance'].create(vals)
|
||||
attendance = self.env['hr.attendance'].search([('employee_id', '=', self.id), ('check_out', '=', False)], limit=1)
|
||||
if attendance:
|
||||
attendance.check_out = action_date
|
||||
else:
|
||||
attendance = self.env['hr.attendance'].search([('employee_id', '=', self.id), ('check_out', '=', False)], limit=1)
|
||||
if attendance:
|
||||
attendance.check_out = action_date
|
||||
else:
|
||||
raise exceptions.UserError(_('Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
|
||||
'Your attendances have probably been modified manually by human resources.') % {'empl_name': self.name, })
|
||||
return attendance
|
||||
raise exceptions.UserError(_('Cannot perform check out on %(empl_name)s, could not find corresponding check in. '
|
||||
'Your attendances have probably been modified manually by human resources.') % {'empl_name': self.sudo().name, })
|
||||
return attendance
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
@@ -19,7 +19,6 @@ class User(models.Model):
|
||||
'hours_last_month',
|
||||
'attendance_state',
|
||||
]
|
||||
init_res = super(User, self).__init__(pool, cr)
|
||||
super(User, self).__init__(pool, cr)
|
||||
# duplicate list to avoid modifying the original reference
|
||||
type(self).SELF_READABLE_FIELDS = type(self).SELF_READABLE_FIELDS + attendance_readable_fields
|
||||
return init_res
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
import time
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestHrAttendance(TransactionCase):
|
||||
"""Tests for attendance date ranges validity"""
|
||||
@@ -12,6 +13,10 @@ class TestHrAttendance(TransactionCase):
|
||||
self.attendance = self.env['hr.attendance']
|
||||
self.test_employee = self.browse_ref('hr.employee_qdp')
|
||||
# demo data contains set up for self.test_employee
|
||||
self.open_attendance = self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
'check_in': time.strftime('%Y-%m-10 10:00'),
|
||||
})
|
||||
|
||||
def test_attendance_in_before_out(self):
|
||||
# Make sure check_out is before check_in
|
||||
@@ -24,10 +29,6 @@ class TestHrAttendance(TransactionCase):
|
||||
|
||||
def test_attendance_no_check_out(self):
|
||||
# Make sure no second attandance without check_out can be created
|
||||
self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
'check_in': time.strftime('%Y-%m-10 10:00'),
|
||||
})
|
||||
with self.assertRaises(Exception):
|
||||
self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
@@ -68,10 +69,6 @@ class TestHrAttendance(TransactionCase):
|
||||
})
|
||||
|
||||
def test_attendance_5(self):
|
||||
self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
'check_in': time.strftime('%Y-%m-10 10:00'),
|
||||
})
|
||||
with self.assertRaises(Exception):
|
||||
self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
@@ -86,11 +83,7 @@ class TestHrAttendance(TransactionCase):
|
||||
'check_in': time.strftime('%Y-%m-10 11:00'),
|
||||
'check_out': time.strftime('%Y-%m-10 12:00'),
|
||||
})
|
||||
open_attendance = self.attendance.create({
|
||||
'employee_id': self.test_employee.id,
|
||||
'check_in': time.strftime('%Y-%m-10 10:00'),
|
||||
})
|
||||
with self.assertRaises(Exception):
|
||||
open_attendance.write({
|
||||
self.open_attendance.write({
|
||||
'check_out': time.strftime('%Y-%m-10 11:30'),
|
||||
})
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from odoo import fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Datetime
|
||||
|
||||
from odoo import models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_employee
|
||||
from . import hr_contract
|
||||
from . import res_users
|
||||
|
||||
@@ -9,42 +9,6 @@ from odoo.exceptions import ValidationError
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
|
||||
medic_exam = fields.Date(string='Medical Examination Date', groups="hr.group_hr_user")
|
||||
vehicle = fields.Char(string='Company Vehicle', groups="hr.group_hr_user")
|
||||
contract_ids = fields.One2many('hr.contract', 'employee_id', string='Employee Contracts')
|
||||
contract_id = fields.Many2one('hr.contract', string='Current Contract', help='Current contract of the employee')
|
||||
contracts_count = fields.Integer(compute='_compute_contracts_count', string='Contract Count')
|
||||
|
||||
def _compute_contracts_count(self):
|
||||
# read_group as sudo, since contract count is displayed on form view
|
||||
contract_data = self.env['hr.contract'].sudo().read_group([('employee_id', 'in', self.ids)], ['employee_id'], ['employee_id'])
|
||||
result = dict((data['employee_id'][0], data['employee_id_count']) for data in contract_data)
|
||||
for employee in self:
|
||||
employee.contracts_count = result.get(employee.id, 0)
|
||||
|
||||
def _get_contracts(self, date_from, date_to, states=['open', 'pending']):
|
||||
"""
|
||||
Returns the contracts of the employee between date_from and date_to
|
||||
"""
|
||||
return self.env['hr.contract'].search([
|
||||
'&', '&', '&',
|
||||
('employee_id', 'in', self.ids),
|
||||
('state', 'in', states),
|
||||
('date_start', '<=', date_to),
|
||||
'|', ('date_end', '=', False), ('date_end', '>=', date_from)
|
||||
])
|
||||
|
||||
@api.model
|
||||
def _get_all_contracts(self, date_from, date_to, states=['open', 'pending']):
|
||||
"""
|
||||
Returns the contracts of all employees between date_from and date_to
|
||||
"""
|
||||
return self.search([])._get_contracts(date_from, date_to, states=states)
|
||||
|
||||
|
||||
class Contract(models.Model):
|
||||
_name = 'hr.contract'
|
||||
_description = 'Contract'
|
||||
@@ -155,7 +119,7 @@ class Contract(models.Model):
|
||||
def write(self, vals):
|
||||
if vals.get('state') == 'open':
|
||||
for contract in self:
|
||||
contract.employee_id.contract_id = contract
|
||||
contract.employee_id.sudo().write({'contract_id': contract.id})
|
||||
return super(Contract, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
|
||||
medic_exam = fields.Date(string='Medical Examination Date', groups="hr.group_hr_user")
|
||||
vehicle = fields.Char(string='Company Vehicle', groups="hr.group_hr_user")
|
||||
contract_ids = fields.One2many('hr.contract', 'employee_id', string='Employee Contracts')
|
||||
contract_id = fields.Many2one('hr.contract', string='Current Contract',
|
||||
groups="hr.group_hr_user", help='Current contract of the employee')
|
||||
contracts_count = fields.Integer(compute='_compute_contracts_count', string='Contract Count')
|
||||
|
||||
def _compute_contracts_count(self):
|
||||
# read_group as sudo, since contract count is displayed on form view
|
||||
contract_data = self.env['hr.contract'].sudo().read_group([('employee_id', 'in', self.ids)], ['employee_id'], ['employee_id'])
|
||||
result = dict((data['employee_id'][0], data['employee_id_count']) for data in contract_data)
|
||||
for employee in self:
|
||||
employee.contracts_count = result.get(employee.id, 0)
|
||||
|
||||
def _get_contracts(self, date_from, date_to, states=['open', 'pending']):
|
||||
"""
|
||||
Returns the contracts of the employee between date_from and date_to
|
||||
"""
|
||||
return self.env['hr.contract'].search([
|
||||
'&', '&', '&',
|
||||
('employee_id', 'in', self.ids),
|
||||
('state', 'in', states),
|
||||
('date_start', '<=', date_to),
|
||||
'|', ('date_end', '=', False), ('date_end', '>=', date_from)
|
||||
])
|
||||
|
||||
@api.model
|
||||
def _get_all_contracts(self, date_from, date_to, states=['open', 'pending']):
|
||||
"""
|
||||
Returns the contracts of all employees between date_from and date_to
|
||||
"""
|
||||
return self.search([])._get_contracts(date_from, date_to, states=states)
|
||||
@@ -266,7 +266,7 @@
|
||||
id="hr_menu_contract"
|
||||
name="Contracts"
|
||||
action="action_hr_contract"
|
||||
parent="hr.menu_hr_root"
|
||||
parent="hr.menu_hr_employee_payroll"
|
||||
sequence="4"
|
||||
groups="hr_contract.group_hr_contract_manager"/>
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ class Employee(models.Model):
|
||||
self.expense_manager_id = manager
|
||||
|
||||
|
||||
class EmployeePublic(models.Model):
|
||||
_inherit = 'hr.employee.public'
|
||||
|
||||
expense_manager_id = fields.Many2one('res.users', readonly=True)
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
_inherit = ['res.users']
|
||||
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
import base64
|
||||
import io
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import request, route, Controller
|
||||
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import request, route, Controller
|
||||
|
||||
|
||||
|
||||
class HrFleet(Controller):
|
||||
|
||||
@route(["/fleet/print_claim_report/<int:employee_id>"], type='http', auth='user')
|
||||
def get_claim_report_user(self, employee_id, **post):
|
||||
if not request.env.user.has_group('fleet.fleet_group_manager'):
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
@@ -26,7 +26,7 @@ class Employee(models.Model):
|
||||
driver_ids = (self.mapped('user_id.partner_id') | self.sudo().mapped('address_home_id')).ids
|
||||
fleet_data = self.env['fleet.vehicle.assignation.log'].read_group(
|
||||
domain=[('driver_id', 'in', driver_ids)], fields=['driver_id'], groupby=['driver_id'])
|
||||
mapped_data = dict([(m['driver_id'][0], m['driver_id_count']) for m in fleet_data])
|
||||
mapped_data = {m['driver_id'][0]: m['driver_id_count'] for m in fleet_data}
|
||||
for employee in self:
|
||||
drivers = employee.user_id.partner_id | employee.sudo().address_home_id
|
||||
employee.employee_cars_count = sum(mapped_data.get(pid, 0) for pid in drivers.ids)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<button name="action_open_employee_cars" type="object"
|
||||
class="oe_stat_button" icon="fa-car"
|
||||
class="oe_stat_button" icon="fa-car" groups="fleet.fleet_group_manager"
|
||||
attrs="{'invisible': [('employee_cars_count','=', 0)]}">
|
||||
<field name="employee_cars_count" widget="statinfo" />
|
||||
</button>
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
|
||||
from . import gamification
|
||||
from . import hr_employee
|
||||
from . import res_users
|
||||
|
||||
@@ -39,6 +39,6 @@ class GamificationBadge(models.Model):
|
||||
'name': 'Granted Employees',
|
||||
'view_mode': 'kanban,tree,form',
|
||||
'view_type': 'form',
|
||||
'res_model': 'hr.employee',
|
||||
'res_model': 'hr.employee.public',
|
||||
'domain': [('id', 'in', employee_ids)]
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_inherit = "hr.employee.base"
|
||||
|
||||
goal_ids = fields.One2many('gamification.goal', string='Employee HR Goals', compute='_compute_employee_goals')
|
||||
badge_ids = fields.One2many(
|
||||
@@ -36,10 +36,3 @@ class HrEmployee(models.Model):
|
||||
])
|
||||
employee.has_badges = bool(badge_ids)
|
||||
employee.badge_ids = badge_ids
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
goal_ids = fields.One2many('gamification.goal', 'user_id')
|
||||
badge_ids = fields.One2many('gamification.badge.user', 'user_id')
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
goal_ids = fields.One2many('gamification.goal', 'user_id')
|
||||
badge_ids = fields.One2many('gamification.badge.user', 'user_id')
|
||||
@@ -25,4 +25,28 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.view.form.inherit</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="inherit_id" ref="hr.hr_employee_public_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//page[@name='public']" position="after">
|
||||
<page string="Received Badges" attrs="{'invisible': [('user_id', '=', False)]}">
|
||||
<field name="has_badges" invisible="1"/>
|
||||
<button string="Grant a Badge" type="action" name="%(action_reward_wizard)d"/> to reward this employee for a good action
|
||||
<div class="o_field_nocontent" attrs="{'invisible': [('has_badges', '=', True)]}">
|
||||
<p>
|
||||
Grant this employee his first badge
|
||||
</p><p class="oe_grey">
|
||||
Badges are rewards of good work. Give them to people you believe deserve it.
|
||||
</p>
|
||||
</div>
|
||||
<field name="badge_ids" mode="kanban" widget="many2many"/>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import resource
|
||||
from . import hr
|
||||
from . import hr_employee
|
||||
from . import hr_department
|
||||
from . import hr_leave
|
||||
from . import hr_leave_allocation
|
||||
from . import hr_leave_type
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
|
||||
_inherit = 'hr.department'
|
||||
|
||||
absence_of_today = fields.Integer(
|
||||
compute='_compute_leave_count', string='Absence by Today')
|
||||
leave_to_approve_count = fields.Integer(
|
||||
compute='_compute_leave_count', string='Time Off to Approve')
|
||||
allocation_to_approve_count = fields.Integer(
|
||||
compute='_compute_leave_count', string='Allocation to Approve')
|
||||
total_employee = fields.Integer(
|
||||
compute='_compute_total_employee', string='Total Employee')
|
||||
|
||||
@api.multi
|
||||
def _compute_leave_count(self):
|
||||
Requests = self.env['hr.leave']
|
||||
Allocations = self.env['hr.leave.allocation']
|
||||
today_date = datetime.datetime.utcnow().date()
|
||||
today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day
|
||||
today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59))
|
||||
|
||||
leave_data = Requests.read_group(
|
||||
[('department_id', 'in', self.ids),
|
||||
('state', '=', 'confirm')],
|
||||
['department_id'], ['department_id'])
|
||||
allocation_data = Allocations.read_group(
|
||||
[('department_id', 'in', self.ids),
|
||||
('state', '=', 'confirm')],
|
||||
['department_id'], ['department_id'])
|
||||
absence_data = Requests.read_group(
|
||||
[('department_id', 'in', self.ids), ('state', 'not in', ['cancel', 'refuse']),
|
||||
('date_from', '<=', today_end), ('date_to', '>=', today_start)],
|
||||
['department_id'], ['department_id'])
|
||||
|
||||
res_leave = dict((data['department_id'][0], data['department_id_count']) for data in leave_data)
|
||||
res_allocation = dict((data['department_id'][0], data['department_id_count']) for data in allocation_data)
|
||||
res_absence = dict((data['department_id'][0], data['department_id_count']) for data in absence_data)
|
||||
|
||||
for department in self:
|
||||
department.leave_to_approve_count = res_leave.get(department.id, 0)
|
||||
department.allocation_to_approve_count = res_allocation.get(department.id, 0)
|
||||
department.absence_of_today = res_absence.get(department.id, 0)
|
||||
|
||||
@api.multi
|
||||
def _compute_total_employee(self):
|
||||
emp_data = self.env['hr.employee'].read_group([('department_id', 'in', self.ids)], ['department_id'], ['department_id'])
|
||||
result = dict((data['department_id'][0], data['department_id_count']) for data in emp_data)
|
||||
for department in self:
|
||||
department.total_employee = result.get(department.id, 0)
|
||||
@@ -2,67 +2,13 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.float_utils import float_round
|
||||
|
||||
|
||||
# YTI TODO: Split into 2 files
|
||||
class Department(models.Model):
|
||||
|
||||
_inherit = 'hr.department'
|
||||
|
||||
absence_of_today = fields.Integer(
|
||||
compute='_compute_leave_count', string='Absence by Today')
|
||||
leave_to_approve_count = fields.Integer(
|
||||
compute='_compute_leave_count', string='Time Off to Approve')
|
||||
allocation_to_approve_count = fields.Integer(
|
||||
compute='_compute_leave_count', string='Allocation to Approve')
|
||||
total_employee = fields.Integer(
|
||||
compute='_compute_total_employee', string='Total Employee')
|
||||
|
||||
@api.multi
|
||||
def _compute_leave_count(self):
|
||||
Requests = self.env['hr.leave']
|
||||
Allocations = self.env['hr.leave.allocation']
|
||||
today_date = datetime.datetime.utcnow().date()
|
||||
today_start = fields.Datetime.to_string(today_date) # get the midnight of the current utc day
|
||||
today_end = fields.Datetime.to_string(today_date + relativedelta(hours=23, minutes=59, seconds=59))
|
||||
|
||||
leave_data = Requests.read_group(
|
||||
[('department_id', 'in', self.ids),
|
||||
('state', '=', 'confirm')],
|
||||
['department_id'], ['department_id'])
|
||||
allocation_data = Allocations.read_group(
|
||||
[('department_id', 'in', self.ids),
|
||||
('state', '=', 'confirm')],
|
||||
['department_id'], ['department_id'])
|
||||
absence_data = Requests.read_group(
|
||||
[('department_id', 'in', self.ids), ('state', 'not in', ['cancel', 'refuse']),
|
||||
('date_from', '<=', today_end), ('date_to', '>=', today_start)],
|
||||
['department_id'], ['department_id'])
|
||||
|
||||
res_leave = dict((data['department_id'][0], data['department_id_count']) for data in leave_data)
|
||||
res_allocation = dict((data['department_id'][0], data['department_id_count']) for data in allocation_data)
|
||||
res_absence = dict((data['department_id'][0], data['department_id_count']) for data in absence_data)
|
||||
|
||||
for department in self:
|
||||
department.leave_to_approve_count = res_leave.get(department.id, 0)
|
||||
department.allocation_to_approve_count = res_allocation.get(department.id, 0)
|
||||
department.absence_of_today = res_absence.get(department.id, 0)
|
||||
|
||||
@api.multi
|
||||
def _compute_total_employee(self):
|
||||
emp_data = self.env['hr.employee'].read_group([('department_id', 'in', self.ids)], ['department_id'], ['department_id'])
|
||||
result = dict((data['department_id'][0], data['department_id_count']) for data in emp_data)
|
||||
for department in self:
|
||||
department.total_employee = result.get(department.id, 0)
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
_inherit = "hr.employee"
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_inherit = "hr.employee.base"
|
||||
|
||||
def _group_hr_user_domain(self):
|
||||
group = self.env.ref('hr_holidays.group_hr_holidays_team_leader', raise_if_not_found=False)
|
||||
@@ -173,7 +119,7 @@ class Employee(models.Model):
|
||||
|
||||
@api.onchange('parent_id')
|
||||
def _onchange_parent_id(self):
|
||||
super(Employee, self)._onchange_parent_id()
|
||||
super(HrEmployeeBase, self)._onchange_parent_id()
|
||||
previous_manager = self._origin.parent_id.user_id
|
||||
manager = self.parent_id.user_id
|
||||
if manager and manager.has_group('hr.group_hr_user') and (self.leave_manager_id == previous_manager or not self.leave_manager_id):
|
||||
@@ -199,7 +145,7 @@ class Employee(models.Model):
|
||||
return [('id', 'in', holidays.mapped('employee_id').ids)]
|
||||
|
||||
def write(self, values):
|
||||
res = super(Employee, self).write(values)
|
||||
res = super(HrEmployeeBase, self).write(values)
|
||||
today_date = fields.Datetime.now()
|
||||
if 'parent_id' in values or 'department_id' in values:
|
||||
hr_vals = {}
|
||||
@@ -207,7 +153,7 @@ class Employee(models.Model):
|
||||
hr_vals['manager_id'] = values['parent_id']
|
||||
if values.get('department_id') is not None:
|
||||
hr_vals['department_id'] = values['department_id']
|
||||
holidays = self.env['hr.leave'].search(['|',('state', 'in', ['draft', 'confirm']),('date_from', '>', today_date), ('employee_id', 'in', self.ids)])
|
||||
holidays = self.env['hr.leave'].search(['|', ('state', 'in', ['draft', 'confirm']), ('date_from', '>', today_date), ('employee_id', 'in', self.ids)])
|
||||
holidays.write(hr_vals)
|
||||
allocations = self.env['hr.leave.allocation'].search([('state', 'in', ['draft', 'confirm']), ('employee_id', 'in', self.ids)])
|
||||
allocations.write(hr_vals)
|
||||
@@ -592,7 +592,7 @@
|
||||
<field name="name">Time Off</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hr.leave</field>
|
||||
<field name="binding_model_id" ref="model_hr_employee"/>
|
||||
<field name="binding_model_id" ref="hr.model_hr_employee"/>
|
||||
<field name="view_mode">tree,kanban,form,calendar,activity</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="domain">[]</field>
|
||||
|
||||
@@ -177,6 +177,52 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.leave.form.inherit</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="inherit_id" ref="hr.hr_employee_public_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='coach_id']" position="after">
|
||||
<field name="leave_manager_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
<field name="current_leave_id" invisible="1"/>
|
||||
<field name="show_leaves" invisible="1"/>
|
||||
<field name="is_absent" invisible="1"/>
|
||||
<button name="%(hr_leave_action_new_request)d" type="action"
|
||||
class="oe_stat_button"
|
||||
attrs="{'invisible': [('is_absent', '=', False)]}">
|
||||
<div role="img" class="fa fa-fw fa-circle o_button_icon text-danger" aria-label="Absent Until" title="Absent Until"/>
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="leave_date_to"/>
|
||||
</span>
|
||||
<span class="o_stat_text">
|
||||
Absent Until
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="%(hr_leave_action_new_request)d"
|
||||
type="action"
|
||||
class="oe_stat_button"
|
||||
icon="fa-calendar"
|
||||
attrs="{'invisible': [('show_leaves','=', False)]}"
|
||||
groups="base.group_user"
|
||||
help="Remaining leaves">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="allocation_used_count" digits="[42,1]"/>/
|
||||
<field name="allocation_count" digits="[42,1]"/>
|
||||
</span>
|
||||
<span class="o_stat_text">
|
||||
Time Off
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_users_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.user.preferences.view.form.leave.inherit</field>
|
||||
<field name="model">res.users</field>
|
||||
|
||||
@@ -19,7 +19,7 @@ class HrOrgChartController(http.Controller):
|
||||
else:
|
||||
cids = [request.env.company.id]
|
||||
|
||||
Employee = request.env['hr.employee'].with_context(allowed_company_ids=cids)
|
||||
Employee = request.env['hr.employee.public'].with_context(allowed_company_ids=cids)
|
||||
# check and raise
|
||||
if not Employee.check_access_rights('read', raise_exception=False):
|
||||
return None
|
||||
@@ -35,13 +35,19 @@ class HrOrgChartController(http.Controller):
|
||||
return dict(
|
||||
id=employee.id,
|
||||
name=employee.name,
|
||||
link='/mail/view?model=hr.employee&res_id=%s' % employee.id,
|
||||
link='/mail/view?model=%s&res_id=%s' % ('hr.employee.public', employee.id,),
|
||||
job_id=job.id,
|
||||
job_name=job.name or '',
|
||||
direct_sub_count=len(employee.child_ids),
|
||||
indirect_sub_count=employee.child_all_count,
|
||||
)
|
||||
|
||||
@http.route('/hr/get_redirect_model', type='json', auth='user')
|
||||
def get_redirect_model(self):
|
||||
if request.env['hr.employee'].check_access_rights('read', raise_exception=False):
|
||||
return 'hr.employee'
|
||||
return 'hr.employee.public'
|
||||
|
||||
@http.route('/hr/get_org_chart', type='json', auth='user')
|
||||
def get_org_chart(self, employee_id, **kw):
|
||||
|
||||
@@ -50,7 +56,7 @@ class HrOrgChartController(http.Controller):
|
||||
return {}
|
||||
|
||||
# compute employee data for org chart
|
||||
ancestors, current = request.env['hr.employee'], employee
|
||||
ancestors, current = request.env['hr.employee.public'], employee
|
||||
while current.parent_id and len(ancestors) < self._managers_level+1:
|
||||
ancestors += current.parent_id
|
||||
current = current.parent_id
|
||||
@@ -81,8 +87,10 @@ class HrOrgChartController(http.Controller):
|
||||
return {}
|
||||
|
||||
if subordinates_type == 'direct':
|
||||
return employee.child_ids.ids
|
||||
res = employee.child_ids.ids
|
||||
elif subordinates_type == 'indirect':
|
||||
return (employee.subordinate_ids - employee.child_ids).ids
|
||||
res = (employee.subordinate_ids - employee.child_ids).ids
|
||||
else:
|
||||
return employee.subordinate_ids.ids
|
||||
res = employee.subordinate_ids.ids
|
||||
|
||||
return res
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr_org_chart_mixin
|
||||
from . import hr_employee
|
||||
|
||||
@@ -1,43 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Employee(models.Model):
|
||||
_name = "hr.employee"
|
||||
_inherit = "hr.employee"
|
||||
_inherit = ["hr.employee"]
|
||||
|
||||
child_all_count = fields.Integer(
|
||||
'Indirect Surbordinates Count',
|
||||
compute='_compute_subordinates', store=False)
|
||||
|
||||
subordinate_ids = fields.One2many('hr.employee', string='Subordinates', compute='_compute_subordinates', help="Direct and indirect subordinates", groups='base.group_user')
|
||||
subordinate_ids = fields.One2many('hr.employee', string='Subordinates', compute='_compute_subordinates', help="Direct and indirect subordinates")
|
||||
|
||||
|
||||
def _get_subordinates(self, parents=None):
|
||||
"""
|
||||
Helper function to compute subordinates_ids.
|
||||
Get all subordinates (direct and indirect) of an employee.
|
||||
An employee can be a manager of his own manager (recursive hierarchy; e.g. the CEO is manager of everyone but is also
|
||||
member of the RD department, managed by the CTO itself managed by the CEO).
|
||||
In that case, the manager in not counted as a subordinate if it's in the 'parents' set.
|
||||
"""
|
||||
class HrEmployeePublic(models.Model):
|
||||
_inherit = ["hr.employee.public"]
|
||||
|
||||
if not parents:
|
||||
parents = self.env['hr.employee']
|
||||
|
||||
indirect_subordinates = self.env['hr.employee']
|
||||
parents |= self
|
||||
direct_subordinates = self.child_ids - parents
|
||||
for child in direct_subordinates:
|
||||
child_subordinate = child._get_subordinates(parents=parents)
|
||||
child.subordinate_ids = child_subordinate
|
||||
indirect_subordinates |= child_subordinate
|
||||
return indirect_subordinates | direct_subordinates
|
||||
|
||||
|
||||
@api.depends('child_ids', 'child_ids.child_all_count')
|
||||
def _compute_subordinates(self):
|
||||
for employee in self:
|
||||
employee.subordinate_ids = employee._get_subordinates()
|
||||
employee.child_all_count = len(employee.subordinate_ids)
|
||||
subordinate_ids = fields.One2many('hr.employee.public', string='Subordinates', compute='_compute_subordinates', help="Direct and indirect subordinates")
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class HrEmployeeBase(models.AbstractModel):
|
||||
_inherit = "hr.employee.base"
|
||||
|
||||
child_all_count = fields.Integer(
|
||||
'Indirect Surbordinates Count',
|
||||
compute='_compute_subordinates', store=False)
|
||||
|
||||
def _get_subordinates(self, parents=None):
|
||||
"""
|
||||
Helper function to compute subordinates_ids.
|
||||
Get all subordinates (direct and indirect) of an employee.
|
||||
An employee can be a manager of his own manager (recursive hierarchy; e.g. the CEO is manager of everyone but is also
|
||||
member of the RD department, managed by the CTO itself managed by the CEO).
|
||||
In that case, the manager in not counted as a subordinate if it's in the 'parents' set.
|
||||
"""
|
||||
if not parents:
|
||||
parents = self.env[self._name]
|
||||
|
||||
indirect_subordinates = self.env[self._name]
|
||||
parents |= self
|
||||
direct_subordinates = self.child_ids - parents
|
||||
for child in direct_subordinates:
|
||||
child_subordinate = child._get_subordinates(parents=parents)
|
||||
child.subordinate_ids = child_subordinate
|
||||
indirect_subordinates |= child_subordinate
|
||||
return indirect_subordinates | direct_subordinates
|
||||
|
||||
|
||||
@api.depends('child_ids', 'child_ids.child_all_count')
|
||||
def _compute_subordinates(self):
|
||||
for employee in self:
|
||||
employee.subordinate_ids = employee._get_subordinates()
|
||||
employee.child_all_count = len(employee.subordinate_ids)
|
||||
@@ -24,7 +24,7 @@ var FieldOrgChart = AbstractField.extend({
|
||||
init: function (parent, options) {
|
||||
this._super.apply(this, arguments);
|
||||
this.dm = new concurrency.DropMisordered();
|
||||
this.employee;
|
||||
this.employee = null;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -37,16 +37,16 @@ var FieldOrgChart = AbstractField.extend({
|
||||
* @param {integer} employee_id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getOrgData: function (employee_id) {
|
||||
_getOrgData: function () {
|
||||
var self = this;
|
||||
return this.dm.add(this._rpc({
|
||||
route: '/hr/get_org_chart',
|
||||
params: {
|
||||
employee_id: employee_id,
|
||||
employee_id: this.employee,
|
||||
context: session.user_context,
|
||||
},
|
||||
})).then(function (data) {
|
||||
self.orgData = data;
|
||||
return data;
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -83,10 +83,9 @@ var FieldOrgChart = AbstractField.extend({
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return this._getOrgData(this.employee).then(function () {
|
||||
|
||||
self.orgData['view_employee_id'] = self.recordData.id;
|
||||
self.$el.html(QWeb.render("hr_org_chart", self.orgData));
|
||||
return this._getOrgData().then(function (orgData) {
|
||||
orgData.view_employee_id = self.recordData.id;
|
||||
self.$el.html(QWeb.render("hr_org_chart", orgData));
|
||||
self.$('[data-toggle="popover"]').each(function () {
|
||||
$(this).popover({
|
||||
html: true,
|
||||
@@ -130,7 +129,7 @@ var FieldOrgChart = AbstractField.extend({
|
||||
_onEmployeeMoreManager: function(event) {
|
||||
event.preventDefault();
|
||||
this.employee = parseInt($(event.currentTarget).data('employee-id'));
|
||||
this._render()
|
||||
this._render();
|
||||
},
|
||||
/**
|
||||
* Redirect to the employee form view.
|
||||
@@ -140,16 +139,15 @@ var FieldOrgChart = AbstractField.extend({
|
||||
* @returns {Promise} action loaded
|
||||
*/
|
||||
_onEmployeeRedirect: function (event) {
|
||||
var self = this;
|
||||
event.preventDefault();
|
||||
var employee_id = parseInt($(event.currentTarget).data('employee-id'));
|
||||
return this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
view_type: 'form',
|
||||
view_mode: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'current',
|
||||
res_model: 'hr.employee',
|
||||
res_id: employee_id,
|
||||
return this._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'get_formview_action',
|
||||
args: [employee_id],
|
||||
}).then(function(action) {
|
||||
return self.do_action(action);
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -164,22 +162,23 @@ var FieldOrgChart = AbstractField.extend({
|
||||
var employee_id = parseInt($(event.currentTarget).data('employee-id'));
|
||||
var employee_name = $(event.currentTarget).data('employee-name');
|
||||
var type = $(event.currentTarget).data('type') || 'direct';
|
||||
var self = this
|
||||
var self = this;
|
||||
if (employee_id) {
|
||||
this._getSubordinatesData(employee_id, type).then(function(data) {
|
||||
var domain = [['id', 'in', data]];
|
||||
|
||||
return self.do_action({
|
||||
name: employee_name,
|
||||
type: 'ir.actions.act_window',
|
||||
view_mode: 'kanban,list,form',
|
||||
views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
|
||||
target: 'current',
|
||||
res_model: 'hr.employee',
|
||||
domain: domain,
|
||||
return self._rpc({
|
||||
model: 'hr.employee',
|
||||
method: 'get_formview_action',
|
||||
args: [employee_id],
|
||||
}).then(function(action) {
|
||||
action = _.extend(action, {
|
||||
'view_mode': 'kanban,list,form',
|
||||
'views': [[false, 'kanban'], [false, 'list'], [false, 'form']],
|
||||
'domain': domain,
|
||||
});
|
||||
return self.do_action(action);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
use bg-images to get a clean and centred images -->
|
||||
<a t-if="! is_self"
|
||||
class="o_media_object rounded-circle o_employee_redirect"
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee/' + employee.id + '/image/\')'"
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee.public/' + employee.id + '/image/\')'"
|
||||
t-att-alt="employee.name"
|
||||
t-att-data-employee-id="employee.id"
|
||||
t-att-href="employee.link"/>
|
||||
<div t-if="is_self"
|
||||
class="o_media_object rounded-circle"
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee/' + employee.id + '/image/\')'"/>
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee.public/' + employee.id + '/image/\')'"/>
|
||||
</div>
|
||||
|
||||
<div class="media-body">
|
||||
@@ -151,7 +151,7 @@
|
||||
|
||||
<t t-name="hr_orgchart_emp_popover_title">
|
||||
<div>
|
||||
<span t-att-style='"background-image:url(\"/web/image/hr.employee/" + employee.id + "/image/\")"'/>
|
||||
<span t-att-style='"background-image:url(\"/web/image/hr.employee.public/" + employee.id + "/image/\")"'/>
|
||||
<a href="#" class="float-right o_employee_redirect" t-att-data-employee-id="employee.id"><i class="fa fa-external-link" role="img" aria-label='Redirect' title="Redirect"></i></a>
|
||||
<b><t t-esc="employee.name"/></b>
|
||||
</div>
|
||||
|
||||
@@ -41,6 +41,8 @@ QUnit.module('hr_org_chart', {
|
||||
managers: [],
|
||||
managers_more: false,
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
return this._super(route, args);
|
||||
}
|
||||
@@ -92,6 +94,8 @@ QUnit.module('hr_org_chart', {
|
||||
name: 'Antoine Langlais',
|
||||
}
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
return this._super(route, args);
|
||||
}
|
||||
@@ -153,6 +157,8 @@ QUnit.module('hr_org_chart', {
|
||||
name: 'John Smith',
|
||||
}
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
return this._super(route, args);
|
||||
}
|
||||
@@ -164,4 +170,4 @@ QUnit.module('hr_org_chart', {
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,22 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_employee_public_view_form_inherit_org_chart" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.view.form.inherit.org_chart</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="inherit_id" ref="hr.hr_employee_public_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@id='o_work_employee_main']" position="after">
|
||||
<group>
|
||||
<div id="o_employee_right">
|
||||
<h4 class="o_org_chart_title mb16 mt0">Organization Chart</h4>
|
||||
<field name="child_ids" widget="hr_org_chart"/>
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_users_view_form" model="ir.ui.view">
|
||||
<field name="name">res.users.preferences.view.form.inherit.org_chart</field>
|
||||
<field name="model">res.users</field>
|
||||
|
||||
@@ -16,7 +16,7 @@ class ResCompany(models.Model):
|
||||
hr_presence_state = fields.Selection([
|
||||
('present', 'Present'),
|
||||
('absent', 'Absent'),
|
||||
('to_define', 'To Define')], default='to_define')
|
||||
('to_define', 'To Define')], groups="hr.group_hr_user", default='to_define')
|
||||
last_activity = fields.Date(compute="_compute_last_activity")
|
||||
|
||||
def _compute_last_activity(self):
|
||||
|
||||
@@ -27,6 +27,13 @@ class Employee(models.Model):
|
||||
return res
|
||||
|
||||
|
||||
class EmployeePublic(models.Model):
|
||||
_inherit = 'hr.employee.public'
|
||||
|
||||
resume_line_ids = fields.One2many('hr.resume.line', 'employee_id', string="Resumé lines")
|
||||
employee_skill_ids = fields.One2many('hr.employee.skill', 'employee_id', string="Skills")
|
||||
|
||||
|
||||
class ResumeLine(models.Model):
|
||||
_name = 'hr.resume.line'
|
||||
_description = "Resumé line of an employee"
|
||||
|
||||
@@ -89,6 +89,47 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="hr_employee_public_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">hr.employee.public.view.form.inherit.resume</field>
|
||||
<field name="model">hr.employee.public</field>
|
||||
<field name="inherit_id" ref="hr.hr_employee_public_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='public']" position="before">
|
||||
<page name="public" string="Resumé">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 o_group_resume">
|
||||
<!-- This field uses a custom tree view rendered by the 'hr_resume' widget.
|
||||
Adding fields in the tree arch below makes them accessible to the widget
|
||||
-->
|
||||
<field mode="tree" nolabel="1" name="resume_line_ids" widget="hr_resume">
|
||||
<tree>
|
||||
<field name="line_type_id"/>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
<div class="col-lg-4 o_group_skills">
|
||||
<separator string="Skills"/>
|
||||
<field mode="tree" nolabel="1" name="employee_skill_ids" widget="hr_skills">
|
||||
<tree>
|
||||
<field name="skill_type_id" invisible="1"/>
|
||||
<field name="skill_id"/>
|
||||
<field name="skill_level_id"/>
|
||||
<field name="level_progress" widget="progressbar"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</div>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="res_users_view_form" model="ir.ui.view">
|
||||
<field name="name">hr.user.preferences.form.inherit.hr.skills</field>
|
||||
<field name="model">res.users</field>
|
||||
@@ -272,5 +313,3 @@
|
||||
sequence="3"
|
||||
groups="base.group_no_one"/>
|
||||
</odoo>
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import hr
|
||||
from . import hr_employee
|
||||
from . import hr_timesheet
|
||||
from . import ir_http
|
||||
from . import res_company
|
||||
|
||||
@@ -7,5 +7,6 @@ from odoo import fields, models
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
timesheet_cost = fields.Monetary('Timesheet Cost', currency_field='currency_id', default=0.0)
|
||||
timesheet_cost = fields.Monetary('Timesheet Cost', currency_field='currency_id',
|
||||
groups="hr.group_hr_user", default=0.0)
|
||||
currency_id = fields.Many2one('res.currency', related='company_id.currency_id', readonly=True)
|
||||
@@ -14,3 +14,10 @@ class HrEmployee(models.Model):
|
||||
super(HrEmployee, self)._compute_website_url()
|
||||
for employee in self:
|
||||
employee.website_url = '/aboutus#team'
|
||||
|
||||
class HrEmployeePublic(models.Model):
|
||||
_name = 'hr.employee.public'
|
||||
_inherit = ['hr.employee.public', 'website.published.multi.mixin']
|
||||
|
||||
public_info = fields.Char(string='Public Info')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user