diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py index e6e7d2e47d5..949b6472b1e 100644 --- a/addons/hr/models/hr.py +++ b/addons/hr/models/hr.py @@ -189,7 +189,7 @@ class Employee(models.Model): 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='Subordinates') + child_ids = fields.One2many('hr.employee', 'parent_id', string='Direct subordinates') coach_id = fields.Many2one('hr.employee', 'Coach') category_ids = fields.Many2many( 'hr.employee.category', 'employee_category_rel', @@ -199,12 +199,6 @@ class Employee(models.Model): notes = fields.Text('Notes') color = fields.Integer('Color Index', default=0) - @api.constrains('parent_id') - def _check_parent_id(self): - for employee in self: - if not employee._check_recursion(): - raise ValidationError(_('You cannot create a recursive hierarchy.')) - @api.onchange('job_id') def _onchange_job_id(self): if self.job_id: diff --git a/addons/hr_org_chart/controllers/hr_org_chart.py b/addons/hr_org_chart/controllers/hr_org_chart.py index 5729997636f..20f1c30047a 100644 --- a/addons/hr_org_chart/controllers/hr_org_chart.py +++ b/addons/hr_org_chart/controllers/hr_org_chart.py @@ -7,7 +7,23 @@ from odoo.http import request class HrOrgChartController(http.Controller): - _managers_level = 2 # FP request + _managers_level = 5 # FP request + + def _check_employee(self, employee_id): + if not employee_id: # to check + return None + employee_id = int(employee_id) + + Employee = request.env['hr.employee'] + # check and raise + if not Employee.check_access_rights('read', raise_exception=False): + return None + try: + Employee.browse(employee_id).check_access_rule('read') + except AccessError: + return None + else: + return Employee.browse(employee_id) def _prepare_employee_data(self, employee): job = employee.sudo().job_id @@ -23,32 +39,46 @@ class HrOrgChartController(http.Controller): @http.route('/hr/get_org_chart', type='json', auth='user') def get_org_chart(self, employee_id): - if not employee_id: # to check - return {} - employee_id = int(employee_id) - Employee = request.env['hr.employee'] - # check and raise - if not Employee.check_access_rights('read', raise_exception=False): + employee = self._check_employee(employee_id) + if not employee: # to check return {} - try: - Employee.browse(employee_id).check_access_rule('read') - except AccessError: - return {} - else: - employee = Employee.browse(employee_id) # compute employee data for org chart ancestors, current = request.env['hr.employee'], employee - while current.parent_id: + while current.parent_id and len(ancestors) < self._managers_level+1: ancestors += current.parent_id current = current.parent_id values = dict( self=self._prepare_employee_data(employee), - managers=[self._prepare_employee_data(ancestor) for idx, ancestor in enumerate(ancestors) if idx < self._managers_level], + managers=[ + self._prepare_employee_data(ancestor) + for idx, ancestor in enumerate(ancestors) + if idx < self._managers_level + ], managers_more=len(ancestors) > self._managers_level, children=[self._prepare_employee_data(child) for child in employee.child_ids], ) values['managers'].reverse() return values + + @http.route('/hr/get_subordinates', type='json', auth='user') + def get_subordinates(self, employee_id, subordinates_type=None): + """ + Get employee subordinates. + Possible values for 'subordinates_type': + - 'indirect' + - 'direct' + """ + + employee = self._check_employee(employee_id) + if not employee: # to check + return {} + + if subordinates_type == 'direct': + return employee.child_ids.ids + elif subordinates_type == 'indirect': + return (employee.subordinate_ids - employee.child_ids).ids + else: + return employee.subordinate_ids.ids diff --git a/addons/hr_org_chart/models/hr_employee.py b/addons/hr_org_chart/models/hr_employee.py index ff56b1b1bb3..15fc50645d2 100644 --- a/addons/hr_org_chart/models/hr_employee.py +++ b/addons/hr_org_chart/models/hr_employee.py @@ -3,16 +3,41 @@ from odoo import api, fields, models - class Employee(models.Model): _name = "hr.employee" _inherit = "hr.employee" child_all_count = fields.Integer( 'Indirect Surbordinates Count', - compute='_compute_child_all_count', store=False) + compute='_compute_subordinates', store=False) - @api.depends('child_ids.child_all_count') - def _compute_child_all_count(self): + subordinate_ids = fields.One2many('hr.employee', string='Subordinates', compute='_compute_subordinates', help="Direct and indirect subordinates", groups='base.group_user') + + + 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['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.child_all_count = len(employee.child_ids) + sum(child.child_all_count for child in employee.child_ids) + employee.subordinate_ids = employee._get_subordinates() + employee.child_all_count = len(employee.subordinate_ids) diff --git a/addons/hr_org_chart/static/src/js/hr_org_chart.js b/addons/hr_org_chart/static/src/js/hr_org_chart.js index b889375cac4..70bf295d4f5 100644 --- a/addons/hr_org_chart/static/src/js/hr_org_chart.js +++ b/addons/hr_org_chart/static/src/js/hr_org_chart.js @@ -14,20 +14,21 @@ var FieldOrgChart = AbstractField.extend({ events: { "click .o_employee_redirect": "_onEmployeeRedirect", "click .o_employee_sub_redirect": "_onEmployeeSubRedirect", + "click .o_employee_more_managers": "_onEmployeeMoreManager" }, /** * @constructor * @override */ - init: function () { + init: function (parent, options) { this._super.apply(this, arguments); this.dm = new concurrency.DropMisordered(); + this.employee; }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- - /** * Get the chart data through a rpc call. * @@ -40,12 +41,28 @@ var FieldOrgChart = AbstractField.extend({ return this.dm.add(this._rpc({ route: '/hr/get_org_chart', params: { - employee_id: employee_id, + employee_id: employee_id }, })).then(function (data) { self.orgData = data; }); }, + /** + * Get subordonates of an employee through a rpc call. + * + * @private + * @param {integer} employee_id + * @returns {Deferred} + */ + _getSubordinatesData: function (employee_id, type) { + return this.dm.add(this._rpc({ + route: '/hr/get_subordinates', + params: { + employee_id: employee_id, + subordinates_type: type + }, + })) + }, /** * @override * @private @@ -57,9 +74,14 @@ var FieldOrgChart = AbstractField.extend({ children: [], })); } + else if (!this.employee) { + this.employee = this.recordData.id + } var self = this; - return this._getOrgData(this.recordData.id).then(function () { + 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)); self.$('[data-toggle="popover"]').each(function () { $(this).popover({ @@ -101,6 +123,11 @@ var FieldOrgChart = AbstractField.extend({ // Handlers //-------------------------------------------------------------------------- + _onEmployeeMoreManager: function(event) { + event.preventDefault(); + this.employee = parseInt($(event.currentTarget).data('employee-id')); + this._render() + }, /** * Redirect to the employee form view. * @@ -133,29 +160,22 @@ 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 domain = [['parent_id', '=', employee_id]]; - var name = _.str.sprintf(_t("Direct Subordinates of %s"), employee_name); - if (type === 'total') { - domain = ['&', ['parent_id', 'child_of', employee_id], ['id', '!=', employee_id]]; - name = _.str.sprintf(_t("Subordinates of %s"), employee_name); - } else if (type === 'indirect') { - domain = ['&', '&', - ['parent_id', 'child_of', employee_id], - ['parent_id', '!=', employee_id], - ['id', '!=', employee_id] - ]; - name = _.str.sprintf(_t("Indirect Subordinates of %s"), employee_name); - } + var self = this if (employee_id) { - return this.do_action({ - name: 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, - }); + 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, + }); + }) + } }, }); diff --git a/addons/hr_org_chart/static/src/xml/hr_org_chart.xml b/addons/hr_org_chart/static/src/xml/hr_org_chart.xml index e4e7f9fa231..fa5db5f32a8 100644 --- a/addons/hr_org_chart/static/src/xml/hr_org_chart.xml +++ b/addons/hr_org_chart/static/src/xml/hr_org_chart.xml @@ -3,7 +3,7 @@
- +