diff --git a/addons/hr/data/hr_data.xml b/addons/hr/data/hr_data.xml index 638071a77ac..699b25bf890 100644 --- a/addons/hr/data/hr_data.xml +++ b/addons/hr/data/hr_data.xml @@ -1,8 +1,8 @@ - + - + mail.channel notification @@ -16,9 +16,10 @@ Leave Management (keep track of employee leaves), Expense Management (manage emp Administration + Sales - + diff --git a/addons/hr/data/hr_demo.xml b/addons/hr/data/hr_demo.xml index c9e20c4cbb0..4557af3354b 100644 --- a/addons/hr/data/hr_demo.xml +++ b/addons/hr/data/hr_demo.xml @@ -1,9 +1,7 @@ - + - - @@ -64,7 +62,7 @@ Good knowledge of Python Good knowledge of HTML and Javascript Knowledge of UML-like modeling Good language skills, other than English (Dutch and French preferred, others welcome) - + @@ -291,7 +289,7 @@ If you have development competencies, we can propose you specific traineeships+3282823500 - + Jan Van Eyck @@ -301,7 +299,7 @@ If you have development competencies, we can propose you specific traineeshipsjve@openerp.com - + Jean-Pierre Carnaud @@ -311,7 +309,7 @@ If you have development competencies, we can propose you specific traineeshipsjep@openerp.com - + John Doe @@ -321,7 +319,7 @@ If you have development competencies, we can propose you specific traineeshipsjod@openerp.com - + João Gomer @@ -331,7 +329,7 @@ If you have development competencies, we can propose you specific traineeshipsjog@openerp.com - + Juan Gomez @@ -340,7 +338,7 @@ If you have development competencies, we can propose you specific traineeships+3282823500 - + Luigi Rondi @@ -350,7 +348,7 @@ If you have development competencies, we can propose you specific traineeshipslur@openerp.com - + Николай Петра @@ -359,7 +357,7 @@ If you have development competencies, we can propose you specific traineeships+3282823500 - + 佐藤さくら (demo_ja_JP) @@ -368,7 +366,7 @@ If you have development competencies, we can propose you specific traineeships+3282823500 - + 趙生 (demo_zh_CN) @@ -378,4 +376,4 @@ If you have development competencies, we can propose you specific traineeships - + diff --git a/addons/hr/data/web_tip_data.xml b/addons/hr/data/web_tip_data.xml index 2c675d52b36..df0911fad25 100644 --- a/addons/hr/data/web_tip_data.xml +++ b/addons/hr/data/web_tip_data.xml @@ -1,7 +1,7 @@ - + - + Get all applications related to this job position. hr.job @@ -11,7 +11,7 @@ bottom - + Click here to change the recruitment goal! hr.job @@ -21,4 +21,4 @@ bottom - + diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py index ae0215a92da..87f508828dd 100644 --- a/addons/hr/models/hr.py +++ b/addons/hr/models/hr.py @@ -3,132 +3,97 @@ import logging -import openerp -from openerp import api -from openerp import SUPERUSER_ID -from openerp import tools -from openerp.modules.module import get_module_resource -from openerp.osv import fields, osv -from openerp.tools.translate import _ +from odoo import api, fields, models +from odoo import tools, _ +from odoo.exceptions import ValidationError +from odoo.modules.module import get_module_resource + _logger = logging.getLogger(__name__) -class hr_employee_category(osv.Model): +class EmployeeCategory(models.Model): _name = "hr.employee.category" _description = "Employee Category" - _columns = { - 'name': fields.char("Employee Tag", required=True), - 'color': fields.integer('Color Index'), - 'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'), - } + + 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 !"), + ('name_uniq', 'unique (name)', "Tag name already exists !"), ] -class hr_job(osv.Model): - - def _get_nbr_employees(self, cr, uid, ids, name, args, context=None): - res = {} - for job in self.browse(cr, uid, ids, context=context): - nb_employees = len(job.employee_ids or []) - res[job.id] = { - 'no_of_employee': nb_employees, - 'expected_employees': nb_employees + job.no_of_recruitment, - } - return res - - def _get_job_position(self, cr, uid, ids, context=None): - res = [] - for employee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context): - if employee.job_id: - res.append(employee.job_id.id) - return res +class Job(models.Model): _name = "hr.job" _description = "Job Position" _inherit = ['mail.thread'] - _columns = { - 'name': fields.char('Job Name', required=True, select=True, translate=True), - 'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees', - help='Expected number of employees for this job position after new recruitment.', - store = { - 'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10), - 'hr.employee': (_get_job_position, ['job_id'], 10), - }, type='integer', - multi='_get_nbr_employees'), - 'no_of_employee': fields.function(_get_nbr_employees, string="Current Number of Employees", - help='Number of employees currently occupying this job position.', - store = { - 'hr.employee': (_get_job_position, ['job_id'], 10), - }, type='integer', - multi='_get_nbr_employees'), - 'no_of_recruitment': fields.integer('Expected New Employees', copy=False, - help='Number of new employees you expect to recruit.'), - 'no_of_hired_employee': fields.integer('Hired Employees', copy=False, - help='Number of hired employees for this job position during recruitment phase.'), - 'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'), - 'description': fields.text('Job Description'), - 'requirements': fields.text('Requirements'), - 'department_id': fields.many2one('hr.department', 'Department'), - 'company_id': fields.many2one('res.company', 'Company'), - 'state': fields.selection([('recruit', 'Recruitment in Progress'), ('open', 'Recruitment Closed')], - string='Status', readonly=True, required=True, - track_visibility='always', copy=False, - help="Set whether the recruitment process is open or closed for this job position."), - 'write_date': fields.datetime('Update Date', readonly=True), - } - _defaults = { - 'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=ctx), - 'state': 'recruit', - 'no_of_recruitment' : 1, - } + name = fields.Char(string='Job Name', 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.user.company_id) + state = fields.Selection([ + ('recruit', 'Recruitment in Progress'), + ('open', 'Recruitment Closed') + ], string='Status', readonly=True, required=True, track_visibility='always', 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!'), - ] - def set_recruit(self, cr, uid, ids, context=None): - for job in self.browse(cr, uid, ids, context=context): - no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment - self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment}, context=context) + @api.depends('no_of_recruitment', 'employee_ids.job_id') + 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 + 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 - def set_open(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, { + @api.multi + def set_open(self): + return self.write({ 'state': 'open', 'no_of_recruitment': 0, 'no_of_hired_employee': 0 - }, context=context) - return True - - # TDE note: done in new api, because called with new api -> context is a - # frozendict -> error when tryign to manipulate it - @api.model - def create(self, values): - return super(hr_job, self.with_context(mail_create_nosubscribe=True)).create(values) - - def copy(self, cr, uid, id, default=None, context=None): - if default is None: - default = {} - if 'name' not in default: - job = self.browse(cr, uid, id, context=context) - default['name'] = _("%s (copy)") % (job.name) - return super(hr_job, self).copy(cr, uid, id, default=default, context=context) - - # ---------------------------------------- - # Compatibility methods - # ---------------------------------------- - _no_of_employee = _get_nbr_employees # v7 compatibility - job_open = set_open # v7 compatibility - job_recruitment = set_recruit # v7 compatibility + }) -class hr_employee(osv.osv): +class Employee(models.Model): + _name = "hr.employee" _description = "Employee" _order = 'name_related' @@ -137,46 +102,60 @@ class hr_employee(osv.osv): _mail_post_access = 'read' - _columns = { - #we need a related field in order to be able to sort the employee by name - 'name_related': fields.related('resource_id', 'name', type='char', string='Name', readonly=True, store=True), - 'country_id': fields.many2one('res.country', 'Nationality (Country)'), - 'birthday': fields.date("Date of Birth"), - 'ssnid': fields.char('SSN No', help='Social Security Number'), - 'sinid': fields.char('SIN No', help="Social Insurance Number"), - 'identification_id': fields.char('Identification No'), - 'gender': fields.selection([('male', 'Male'), ('female', 'Female'), ('other', 'Other')], 'Gender'), - 'marital': fields.selection([('single', 'Single'), ('married', 'Married'), ('widower', 'Widower'), ('divorced', 'Divorced')], 'Marital Status'), - 'department_id': fields.many2one('hr.department', 'Department'), - 'address_id': fields.many2one('res.partner', 'Working Address'), - 'address_home_id': fields.many2one('res.partner', 'Home Address'), - 'bank_account_id': fields.many2one('res.partner.bank', 'Bank Account Number', domain="[('partner_id','=',address_home_id)]", help="Employee bank salary account"), - 'work_phone': fields.char('Work Phone', readonly=False), - 'mobile_phone': fields.char('Work Mobile', readonly=False), - 'work_email': fields.char('Work Email', size=240), - 'work_location': fields.char('Work Location'), - 'notes': fields.text('Notes'), - 'parent_id': fields.many2one('hr.employee', 'Manager'), - 'category_ids': fields.many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Tags'), - 'child_ids': fields.one2many('hr.employee', 'parent_id', 'Subordinates'), - 'resource_id': fields.many2one('resource.resource', 'Resource', ondelete='cascade', required=True, auto_join=True), - 'coach_id': fields.many2one('hr.employee', 'Coach'), - 'job_id': fields.many2one('hr.job', 'Job Title'), - 'passport_id': fields.char('Passport No'), - 'color': fields.integer('Color Index'), - 'city': fields.related('address_id', 'city', type='char', string='City'), - 'login': fields.related('user_id', 'login', type='char', string='Login', readonly=1), - 'last_login': fields.related('user_id', 'date', type='datetime', string='Latest Connection', readonly=1), - } + @api.model + def _default_image(self): + image_path = get_module_resource('hr', 'static/src/img', 'default_image.png') + return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64')) + + # we need a related field in order to be able to sort the employee by name + name_related = fields.Char('Name', related='resource_id.name', readonly=True, store=True) + country_id = fields.Many2one('res.country', string='Nationality (Country)') + birthday = fields.Date('Date of Birth') + ssnid = fields.Char('SSN No', help='Social Security Number') + sinid = fields.Char('SIN No', help='Social Insurance Number') + identification_id = fields.Char(string='Identification No') + gender = fields.Selection([ + ('male', 'Male'), + ('female', 'Female'), + ('other', 'Other') + ]) + marital = fields.Selection([ + ('single', 'Single'), + ('married', 'Married'), + ('widower', 'Widower'), + ('divorced', 'Divorced') + ], string='Marital Status') + department_id = fields.Many2one('hr.department', string='Department') + address_id = fields.Many2one('res.partner', string='Working Address') + address_home_id = fields.Many2one('res.partner', string='Home Address') + bank_account_id = fields.Many2one('res.partner.bank', string='Bank Account Number', + domain="[('partner_id', '=', address_home_id)]", help='Employee bank salary account') + work_phone = fields.Char('Work Phone') + mobile_phone = fields.Char('Work Mobile') + work_email = fields.Char('Work Email') + work_location = fields.Char('Work Location') + notes = fields.Text('Notes') + parent_id = fields.Many2one('hr.employee', string='Manager') + category_ids = fields.Many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', string='Tags') + child_ids = fields.One2many('hr.employee', 'parent_id', string='Subordinates') + resource_id = fields.Many2one('resource.resource', string='Resource', + ondelete='cascade', required=True, auto_join=True) + coach_id = fields.Many2one('hr.employee', string='Coach') + job_id = fields.Many2one('hr.job', string='Job Title') + passport_id = fields.Char('Passport No') + color = fields.Integer('Color Index', default=0) + city = fields.Char(related='address_id.city') + login = fields.Char(related='user_id.login', readonly=True) + last_login = fields.Datetime(related='user_id.login_date', string='Latest Connection', readonly=True) # image: all image fields are base64 encoded and PIL-supported - image = openerp.fields.Binary("Photo", attachment=True, + image = fields.Binary("Photo", default=_default_image, attachment=True, help="This field holds the image used as photo for the employee, limited to 1024x1024px.") - image_medium = openerp.fields.Binary("Medium-sized photo", attachment=True, + image_medium = fields.Binary("Medium-sized photo", attachment=True, help="Medium-sized photo of the employee. It is automatically "\ "resized as a 128x128px image, with aspect ratio preserved. "\ "Use this field in form views or some kanban views.") - image_small = openerp.fields.Binary("Small-sized photo", attachment=True, + image_small = fields.Binary("Small-sized photo", attachment=True, 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.") @@ -191,67 +170,66 @@ class hr_employee(osv.osv): 'color': 0, } + @api.constrains('parent_id') + def _check_parent_id(self): + for employee in self: + if not employee._check_recursion(): + raise ValidationError(_('Error! You cannot create recursive hierarchy of Employee(s).')) + + @api.onchange('address_id') + def _onchange_address(self): + self.work_phone = self.address_id.phone + self.mobile_phone = self.address_id.mobile + + @api.onchange('company_id') + def _onchange_company(self): + address = self.company_id.partner_id.address_get(['default']) + self.address_id = address['default'] if address else False + + @api.onchange('department_id') + def _onchange_department(self): + self.parent_id = self.department_id.manager_id + + @api.onchange('user_id') + def _onchange_user(self): + self.work_email = self.user_id.email + self.name = self.user_id.name + self.image = self.user_id.image + @api.model def create(self, vals): tools.image_resize_images(vals) - return super(hr_employee, self).create(vals) + return super(Employee, self).create(vals) @api.multi def write(self, vals): tools.image_resize_images(vals) - return super(hr_employee, self).write(vals) + return super(Employee, self).write(vals) - def unlink(self, cr, uid, ids, context=None): - resource_ids = [] - for employee in self.browse(cr, uid, ids, context=context): - resource_ids.append(employee.resource_id.id) - super(hr_employee, self).unlink(cr, uid, ids, context=context) - return self.pool.get('resource.resource').unlink(cr, uid, resource_ids, context=context) + @api.multi + def unlink(self): + resources = self.mapped('resource_id') + super(Employee, self).unlink() + return resources.unlink() - def onchange_address_id(self, cr, uid, ids, address, context=None): - if address: - address = self.pool.get('res.partner').browse(cr, uid, address, context=context) - return {'value': {'work_phone': address.phone, 'mobile_phone': address.mobile}} - return {'value': {}} - - def onchange_company(self, cr, uid, ids, company, context=None): - address_id = False - if company: - company_id = self.pool.get('res.company').browse(cr, uid, company, context=context) - address = self.pool.get('res.partner').address_get(cr, uid, [company_id.partner_id.id], ['contact']) - address_id = address and address['contact'] or False - return {'value': {'address_id': address_id}} - - def onchange_department_id(self, cr, uid, ids, department_id, context=None): - value = {'parent_id': False} - if department_id: - department = self.pool.get('hr.department').browse(cr, uid, department_id) - value['parent_id'] = department.manager_id.id - return {'value': value} - - def onchange_user(self, cr, uid, ids, name, image, user_id, context=None): - if user_id: - user = self.pool['res.users'].browse(cr, uid, user_id, context=context) - values = { - 'name': name or user.name, - 'work_email': user.email, - 'image': image or user.image, - } - return {'value': values} - - def action_follow(self, cr, uid, ids, context=None): + @api.multi + def action_follow(self): """ Wrapper because message_subscribe_users take a user_ids=None - that receive the context without the wrapper. """ - return self.message_subscribe_users(cr, uid, ids, context=context) + that receive the context without the wrapper. + """ + return self.message_subscribe_users() - def action_unfollow(self, cr, uid, ids, context=None): + @api.multi + def action_unfollow(self): """ Wrapper because message_unsubscribe_users take a user_ids=None - that receive the context without the wrapper. """ - return self.message_unsubscribe_users(cr, uid, ids, context=context) + that receive the context without the wrapper. + """ + return self.message_unsubscribe_users() - def _message_get_auto_subscribe_fields(self, cr, uid, updated_fields, auto_follow_fields=None, context=None): + @api.model + def _message_get_auto_subscribe_fields(self, updated_fields, auto_follow_fields=None): """ Overwrite of the original method to always follow user_id field, - even when not track_visibility so that a user will follow it's employee + even when not track_visibility so that a user will follow it's employee """ if auto_follow_fields is None: auto_follow_fields = ['user_id'] @@ -261,103 +239,71 @@ class hr_employee(osv.osv): user_field_lst.append(name) return user_field_lst - _constraints = [(osv.osv._check_recursion, _('Error! You cannot create recursive hierarchy of Employee(s).'), ['parent_id']),] +class Department(models.Model): -class hr_department(osv.osv): _name = "hr.department" - _description = "Department" + _description = "Hr Department" _inherit = ['mail.thread', 'ir.needaction_mixin'] - _columns = { - 'name': fields.char('Department Name', required=True), - 'active': fields.boolean('Active'), - 'company_id': fields.many2one('res.company', 'Company', select=True, required=False), - 'parent_id': fields.many2one('hr.department', 'Parent Department', select=True), - 'child_ids': fields.one2many('hr.department', 'parent_id', 'Child Departments'), - 'manager_id': fields.many2one('hr.employee', 'Manager', track_visibility='onchange'), - 'member_ids': fields.one2many('hr.employee', 'department_id', 'Members', readonly=True), - 'jobs_ids': fields.one2many('hr.job', 'department_id', 'Jobs'), - 'note': fields.text('Note'), - 'color': fields.integer('Color Index'), - } + name = fields.Char('Department Name', required=True) + active = fields.Boolean('Active', default=True) + company_id = fields.Many2one('res.company', string='Company', index=True, default=lambda self: self.env.user.company_id) + 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', track_visibility='onchange') + 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') - _defaults = { - 'active': True, - 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.department', context=c), - } + @api.constrains('parent_id') + def _check_parent_id(self): + if not self._check_recursion(): + raise ValidationError(_('Error! You cannot create recursive departments.')) - _constraints = [ - (osv.osv._check_recursion, _('Error! You cannot create recursive departments.'), ['parent_id']) - ] + @api.multi + def name_get(self): + result = [] + for record in self: + name = record.name + if record.parent_id: + name = "%s / %s" % (record.parent_id.name_get()[1], name) + result.append((record.id, name)) + return result - def name_get(self, cr, uid, ids, context=None): - if not ids: - return [] - if isinstance(ids, (int, long)): - ids = [ids] - if context is None: - context = {} - reads = self.read(cr, uid, ids, ['name','parent_id'], context=context) - res = [] - for record in reads: - name = record['name'] - if record['parent_id']: - name = record['parent_id'][1]+' / '+name - res.append((record['id'], name)) - return res - - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - context = dict(context, mail_create_nosubscribe=True) + @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. - manager_id = vals.get("manager_id") - new_id = super(hr_department, self).create(cr, uid, vals, context=context) - if manager_id: - employee = self.pool.get('hr.employee').browse(cr, uid, manager_id, context=context) - if employee.user_id: - self.message_subscribe_users(cr, uid, [new_id], user_ids=[employee.user_id.id], context=context) - return new_id + 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_users(user_ids=manager.user_id.ids) + return department - def write(self, cr, uid, ids, vals, context=None): + @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 isinstance(ids, (int, long)): - ids = [ids] - employee_ids = [] if 'manager_id' in vals: manager_id = vals.get("manager_id") if manager_id: - employee = self.pool['hr.employee'].browse(cr, uid, manager_id, context=context) - if employee.user_id: - self.message_subscribe_users(cr, uid, ids, user_ids=[employee.user_id.id], context=context) - for department in self.browse(cr, uid, ids, context=context): - employee_ids += self.pool['hr.employee'].search( - cr, uid, [ - ('id', '!=', manager_id), - ('department_id', '=', department.id), - ('parent_id', '=', department.manager_id.id) - ], context=context) - self.pool['hr.employee'].write(cr, uid, employee_ids, {'parent_id': manager_id}, context=context) - return super(hr_department, self).write(cr, uid, ids, vals, context=context) - - -class res_users(osv.osv): - _name = 'res.users' - _inherit = 'res.users' - - def write(self, cr, uid, ids, vals, context=None): - if isinstance(ids, (int, long)): - ids = [ids] - result = super(res_users, self).write(cr, uid, ids, vals, context=context) - employee_obj = self.pool.get('hr.employee') - if vals.get('name'): - for user_id in ids: - if user_id == SUPERUSER_ID: - employee_ids = employee_obj.search(cr, uid, [('user_id', '=', user_id)]) - employee_obj.write(cr, uid, employee_ids, {'name': vals['name']}, context=context) - return result + manager = self.env['hr.employee'].browse(manager_id) + # subscribe the manager user + if manager.user_id: + self.message_subscribe_users(user_ids=manager.user_id.ids) + 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(Department, self).write(vals) diff --git a/addons/hr/models/res_partner.py b/addons/hr/models/res_partner.py index 962a0989e61..8c306f778da 100644 --- a/addons/hr/models/res_partner.py +++ b/addons/hr/models/res_partner.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- -from openerp import api, models +from odoo import api, models class Partner(models.Model): - _name = "res.partner" + _inherit = ['res.partner'] @api.model diff --git a/addons/hr/models/res_users.py b/addons/hr/models/res_users.py index 0b1994f3ab5..f759aba30f5 100644 --- a/addons/hr/models/res_users.py +++ b/addons/hr/models/res_users.py @@ -1,40 +1,48 @@ -from openerp import api -from openerp.osv import fields, osv +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models, fields +from odoo import SUPERUSER_ID -class res_users(osv.Model): - """ Update of res.users class +class User(models.Model): - - add field for the related employee of the user - - if adding groups to an user, check if base.group_user is in it (member of - 'Employee'), create an employee form linked to it. """ - _name = 'res.users' _inherit = ['res.users'] - _columns = { - 'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'), - } + employee_ids = fields.One2many('hr.employee', 'user_id', string='Related employees') - def _message_post_get_eid(self, cr, uid, thread_id, context=None): - assert thread_id, "res.users does not support posting global messages" - if context and 'thread_model' in context: - context = dict(context or {}) - context['thread_model'] = 'hr.employee' - if isinstance(thread_id, (list, tuple)): - thread_id = thread_id[0] - return self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', thread_id)], context=context) + @api.multi + def write(self, vals): + """ When renaming admin user, we want its new name propagated to its related employees """ + result = super(User, self).write(vals) + Employee = self.env['hr.employee'] + if vals.get('name'): + for user in self.filtered(lambda user: user.id == SUPERUSER_ID): + employees = Employee.search([('user_id', '=', user.id)]) + employees.write({'name': vals['name']}) + return result - @api.cr_uid_ids_context - def message_post(self, cr, uid, thread_id, context=None, **kwargs): - """ Redirect the posting of message on res.users to the related employee. + @api.multi + def _get_related_employees(self): + self.ensure_one() + ctx = dict(self.env.context) + if 'thread_model' in ctx: + ctx['thread_model'] = 'hr.employee' + return self.env['hr.employee'].with_context(ctx).search([('user_id', '=', self.id)]) + + @api.multi + def message_post(self, **kwargs): + """ Redirect the posting of message on res.users to the related employees. This is done because when giving the context of Chatter on the - various mailboxes, we do not have access to the current partner_id. """ + various mailboxes, we do not have access to the current partner_id. + """ + self.ensure_one() if kwargs.get('message_type') == 'email': - return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs) - res = None - employee_ids = self._message_post_get_eid(cr, uid, thread_id, context=context) - if not employee_ids: # no employee: fall back on previous behavior - return super(res_users, self).message_post(cr, uid, thread_id, context=context, **kwargs) - for employee_id in employee_ids: - res = self.pool.get('hr.employee').message_post(cr, uid, employee_id, context=context, **kwargs) - return res + return super(User, self).message_post(**kwargs) + message_id = None + employees = self._get_related_employees() + if not employees: # no employee: fall back on previous behavior + return super(User, self).message_post(**kwargs) + for employee in employees: + message_id = employee.message_post(**kwargs) + return message_id diff --git a/addons/hr/security/hr_security.xml b/addons/hr/security/hr_security.xml index 3a598bb4a70..320ca081325 100644 --- a/addons/hr/security/hr_security.xml +++ b/addons/hr/security/hr_security.xml @@ -1,8 +1,7 @@ - - + - + the user will be able to manage his own human resources stuff (leave request, timesheets, ...), if he is linked to an employee in the system. @@ -12,6 +11,7 @@ the user will be able to approve document created by employees. + Manager the user will have an access to the human resources configuration as well as statistic reports. @@ -19,7 +19,7 @@ - + @@ -29,21 +29,21 @@ - Department multi company rule - + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + Job multi company rule - + ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] - + diff --git a/addons/hr/views/hr_templates.xml b/addons/hr/views/hr_templates.xml index b01f006f4e7..1d1ebd71267 100644 --- a/addons/hr/views/hr_templates.xml +++ b/addons/hr/views/hr_templates.xml @@ -1,10 +1,10 @@ - - - - - + + + + + diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml index 61a444a42b1..14024c5bea6 100644 --- a/addons/hr/views/hr_views.xml +++ b/addons/hr/views/hr_views.xml @@ -1,17 +1,35 @@ - + - - - - + + + + + + @@ -39,7 +57,7 @@ - @@ -48,7 +66,7 @@ - + @@ -82,8 +100,8 @@ - - + + @@ -116,7 +134,7 @@ - Employees + hr.employee.search hr.employee @@ -137,8 +155,8 @@ - - HR - Employess Kanban + + hr.employee.kanban hr.employee 10 @@ -229,14 +247,18 @@ - + hr.employee.tree hr.employee child_ids - + @@ -281,6 +303,7 @@ + hr.employee.category.list hr.employee.category @@ -291,14 +314,20 @@ + Employee Tags hr.employee.category form tree,form - + + @@ -354,7 +383,7 @@ - Job + hr.job.search hr.job @@ -373,7 +402,7 @@ - + Job Positions hr.job form @@ -396,7 +425,7 @@ - + Employees hr.employee form @@ -561,4 +590,4 @@ groups="base.group_hr_user"/> - +