# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging 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 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 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!'), ] @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 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): _name = "hr.employee" _description = "Employee" _order = 'name_related' _inherits = {'resource.resource': "resource_id"} _inherit = ['mail.thread'] _mail_post_access = 'read' @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(related='resource_id.name', string="Resource 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 = 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 = 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 = 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.") def _get_default_image(self, cr, uid, context=None): 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')) defaults = { 'active': 1, 'image': _get_default_image, '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(Employee, self).create(vals) @api.multi def write(self, vals): tools.image_resize_images(vals) return super(Employee, self).write(vals) @api.multi def unlink(self): resources = self.mapped('resource_id') super(Employee, self).unlink() return resources.unlink() @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() @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() @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 """ if auto_follow_fields is None: auto_follow_fields = ['user_id'] user_field_lst = [] for name, field in self._fields.items(): if name in auto_follow_fields and name in updated_fields and field.comodel_name == 'res.users': user_field_lst.append(name) return user_field_lst class Department(models.Model): _name = "hr.department" _description = "Hr Department" _inherit = ['mail.thread', 'ir.needaction_mixin'] _order = "name" 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') @api.constrains('parent_id') def _check_parent_id(self): if not self._check_recursion(): raise ValidationError(_('Error! You cannot create recursive departments.')) @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()[0][1], name) result.append((record.id, name)) return result @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_users(user_ids=manager.user_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_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)