summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRomainLibert <rli@odoo.com>2019-04-08 11:41:13 +0200
committerYannick Tivisse <yti@odoo.com>2019-05-31 10:21:20 +0200
commitc9ca3761464413327d2beb697553a3ccd7eef4d1 (patch)
tree74b3bf1db6dcb5eb4c75ce0378a08d08075920ef
parentb2b26f1ff7e4dcf7d0c0ccc36b249045372329c7 (diff)
[IMP] 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.
-rw-r--r--addons/hr/__manifest__.py1
-rw-r--r--addons/hr/models/__init__.py2
-rw-r--r--addons/hr/models/hr_employee.py90
-rw-r--r--addons/hr/models/hr_employee_base.py25
-rw-r--r--addons/hr/models/hr_employee_public.py59
-rw-r--r--addons/hr/security/hr_security.xml7
-rw-r--r--addons/hr/security/ir.model.access.csv3
-rw-r--r--addons/hr/tests/test_self_user_access.py25
-rw-r--r--addons/hr/views/hr_employee_public_views.xml151
-rw-r--r--addons/hr/views/hr_views.xml46
10 files changed, 360 insertions, 49 deletions
diff --git a/addons/hr/__manifest__.py b/addons/hr/__manifest__.py
index c38700c0404..bcbd2fafdf9 100644
--- a/addons/hr/__manifest__.py
+++ b/addons/hr/__manifest__.py
@@ -29,6 +29,7 @@
'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',
diff --git a/addons/hr/models/__init__.py b/addons/hr/models/__init__.py
index dc940941bd1..efe3c1d702c 100644
--- a/addons/hr/models/__init__.py
+++ b/addons/hr/models/__init__.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+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
diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py
index 8f9078b7292..1764b1e0550 100644
--- a/addons/hr/models/hr_employee.py
+++ b/addons/hr/models/hr_employee.py
@@ -12,11 +12,17 @@ from odoo.modules.module import get_module_resource
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
@@ -83,7 +89,6 @@ class HrEmployeePrivate(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(
@@ -99,17 +104,8 @@ class HrEmployeePrivate(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')
@@ -118,22 +114,80 @@ class HrEmployeePrivate(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:
diff --git a/addons/hr/models/hr_employee_base.py b/addons/hr/models/hr_employee_base.py
new file mode 100644
index 00000000000..fec003ccfc6
--- /dev/null
+++ b/addons/hr/models/hr_employee_base.py
@@ -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')
diff --git a/addons/hr/models/hr_employee_public.py b/addons/hr/models/hr_employee_public.py
new file mode 100644
index 00000000000..82799661d0e
--- /dev/null
+++ b/addons/hr/models/hr_employee_public.py
@@ -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()))
diff --git a/addons/hr/security/hr_security.xml b/addons/hr/security/hr_security.xml
index c735d05b1ce..bbafaf7fb38 100644
--- a/addons/hr/security/hr_security.xml
+++ b/addons/hr/security/hr_security.xml
@@ -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"/>
diff --git a/addons/hr/security/ir.model.access.csv b/addons/hr/security/ir.model.access.csv
index 345374b31fe..e28b4065ee3 100644
--- a/addons/hr/security/ir.model.access.csv
+++ b/addons/hr/security/ir.model.access.csv
@@ -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
diff --git a/addons/hr/tests/test_self_user_access.py b/addons/hr/tests/test_self_user_access.py
index 360cf4f5a1e..ab732f9f9e6 100644
--- a/addons/hr/tests/test_self_user_access.py
+++ b/addons/hr/tests/test_self_user_access.py
@@ -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'})
diff --git a/addons/hr/views/hr_employee_public_views.xml b/addons/hr/views/hr_employee_public_views.xml
new file mode 100644
index 00000000000..6b7430c21f0
--- /dev/null
+++ b/addons/hr/views/hr_employee_public_views.xml
@@ -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>
diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml
index da423b9adb7..3a5c490ec70 100644
--- a/addons/hr/views/hr_views.xml
+++ b/addons/hr/views/hr_views.xml
@@ -16,31 +16,38 @@
sequence="0"/>
<menuitem
- id="hr_menu_hr_reports"
- name="Reporting"
- parent="menu_hr_root"
- sequence="95"/>
-
- <menuitem
- id="menu_open_view_employee_list_my"
- action="open_view_employee_list_my"
+ 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_department_tree"
- action="open_module_tree_department"
+ id="menu_hr_employee"
+ name="Company Address"
+ action="hr_employee_public_action"
parent="menu_hr_root"
- sequence="90"
- groups="group_hr_user"/>
+ sequence="4"/>
- <!-- RLI TODO Rename this, it has nothing to do with timesheet -->
<menuitem
- id="menu_hr_reporting_timesheet"
+ id="hr_menu_hr_reports"
name="Reporting"
parent="menu_hr_root"
- groups="group_hr_manager,group_hr_user"
- sequence="99"/>
+ sequence="95"/>
+
+ <menuitem
+ 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"
@@ -65,6 +72,13 @@
sequence="1"/>
<menuitem
+ id="menu_hr_department_tree"
+ action="open_module_tree_department"
+ parent="menu_human_resources_configuration"
+ sequence="2"
+ groups="group_hr_user"/>
+
+ <menuitem
id="menu_config_plan"
name="Activity Planning"
parent="menu_human_resources_configuration"