1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
24 from osv import fields, osv
26 _logger = logging.getLogger(__name__)
28 class hr_employee_category(osv.osv):
30 def name_get(self, cr, uid, ids, context=None):
33 reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
37 if record['parent_id']:
38 name = record['parent_id'][1]+' / '+name
39 res.append((record['id'], name))
42 def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
43 res = self.name_get(cr, uid, ids, context=context)
46 _name = "hr.employee.category"
47 _description = "Employee Category"
49 'name': fields.char("Category", size=64, required=True),
50 'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
51 'parent_id': fields.many2one('hr.employee.category', 'Parent Category', select=True),
52 'child_ids': fields.one2many('hr.employee.category', 'parent_id', 'Child Categories'),
53 'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'),
56 def _check_recursion(self, cr, uid, ids, context=None):
59 cr.execute('select distinct parent_id from hr_employee_category where id IN %s', (tuple(ids), ))
60 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
67 (_check_recursion, 'Error ! You cannot create recursive Categories.', ['parent_id'])
70 hr_employee_category()
72 class hr_job(osv.osv):
74 def _no_of_employee(self, cr, uid, ids, name, args, context=None):
76 for job in self.browse(cr, uid, ids, context=context):
77 nb_employees = len(job.employee_ids or [])
79 'no_of_employee': nb_employees,
80 'expected_employees': nb_employees + job.no_of_recruitment,
84 def _get_job_position(self, cr, uid, ids, context=None):
86 for employee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context):
88 res.append(employee.job_id.id)
92 _description = "Job Description"
94 'name': fields.char('Job Name', size=128, required=True, select=True),
95 'expected_employees': fields.function(_no_of_employee, string='Total Employees',
96 help='Expected number of employees for this job position after new recruitment.',
98 'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
99 'hr.employee': (_get_job_position, ['job_id'], 10),
101 multi='no_of_employee'),
102 'no_of_employee': fields.function(_no_of_employee, string="Number of Employees",
103 help='Number of employees currently occupying this job position.',
105 'hr.employee': (_get_job_position, ['job_id'], 10),
107 multi='no_of_employee'),
108 'no_of_recruitment': fields.float('Expected in Recruitment', help='Number of new employees you expect to recruit.'),
109 'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees'),
110 'description': fields.text('Job Description'),
111 'requirements': fields.text('Requirements'),
112 'department_id': fields.many2one('hr.department', 'Department'),
113 'company_id': fields.many2one('res.company', 'Company'),
114 'state': fields.selection([('open', 'In Position'), ('recruit', 'In Recruitement')], 'Status', readonly=True, required=True,
115 help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
118 'expected_employees': 1,
119 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
124 ('name_company_uniq', 'unique(name, company_id)', 'The name of the job position must be unique per company!'),
128 def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
131 return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}
133 def job_recruitement(self, cr, uid, ids, *args):
134 for job in self.browse(cr, uid, ids):
135 no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
136 self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
139 def job_open(self, cr, uid, ids, *args):
140 self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
145 class hr_employee(osv.osv):
146 _name = "hr.employee"
147 _description = "Employee"
148 _inherits = {'resource.resource': "resource_id"}
150 def _get_image_resized(self, cr, uid, ids, name, args, context=None):
151 result = dict.fromkeys(ids, False)
152 for hr_employee in self.browse(cr, uid, ids, context=context):
153 result[hr_employee.id] = {'image_medium': False, 'image_small': False}
154 if hr_employee.image:
155 result[hr_employee.id]['image_medium'] = tools.resize_image_medium(hr_employee.image)
156 result[hr_employee.id]['image_small'] = tools.resize_image_small(hr_employee.image)
159 def _set_image_resized(self, cr, uid, id, name, value, args, context=None):
161 vals = {'image': value}
163 vals = {'image': tools.resize_image_big(value)}
164 return self.write(cr, uid, [id], vals, context=context)
166 def onchange_image(self, cr, uid, ids, value, context=None):
170 'image_medium': value,
171 'image_small': value,
174 'image': tools.resize_image_big(value),
175 'image_medium': tools.resize_image_medium(value),
176 'image_small': tools.resize_image_small(value),
180 'country_id': fields.many2one('res.country', 'Nationality'),
181 'birthday': fields.date("Date of Birth"),
182 'ssnid': fields.char('SSN No', size=32, help='Social Security Number'),
183 'sinid': fields.char('SIN No', size=32, help="Social Insurance Number"),
184 'identification_id': fields.char('Identification No', size=32),
185 'otherid': fields.char('Other Id', size=64),
186 'gender': fields.selection([('male', 'Male'),('female', 'Female')], 'Gender'),
187 'marital': fields.selection([('single', 'Single'), ('married', 'Married'), ('widower', 'Widower'), ('divorced', 'Divorced')], 'Marital Status'),
188 'department_id':fields.many2one('hr.department', 'Department'),
189 'address_id': fields.many2one('res.partner', 'Working Address'),
190 'address_home_id': fields.many2one('res.partner', 'Home Address'),
191 'bank_account_id':fields.many2one('res.partner.bank', 'Bank Account Number', domain="[('partner_id','=',address_home_id)]", help="Employee bank salary account"),
192 'work_phone': fields.char('Work Phone', size=32, readonly=False),
193 'mobile_phone': fields.char('Work Mobile', size=32, readonly=False),
194 'work_email': fields.char('Work Email', size=240),
195 'work_location': fields.char('Office Location', size=32),
196 'notes': fields.text('Notes'),
197 'parent_id': fields.many2one('hr.employee', 'Manager'),
198 'category_ids': fields.many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Categories'),
199 'child_ids': fields.one2many('hr.employee', 'parent_id', 'Subordinates'),
200 'resource_id': fields.many2one('resource.resource', 'Resource', ondelete='cascade', required=True),
201 'coach_id': fields.many2one('hr.employee', 'Coach'),
202 'job_id': fields.many2one('hr.job', 'Job'),
203 'image': fields.binary("Photo",
204 help="This field holds the photo used as image for the "\
205 "user. The avatar field is used as an interface to "\
206 "access this field. The image is base64 encoded, "\
207 "and PIL-supported. It is stored as a 540x450 px "\
208 "image, in case a bigger image must be used."),
209 'image_medium': fields.function(_get_image_resized, fnct_inv=_set_image_resized,
210 string="Medium-sized photo", type="binary", multi="_get_image_resized",
212 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
214 help="Medium-sized photo of the user. It is automatically "\
215 "resized as a 180x180px image, with aspect ratio keps. "\
216 "Use this field in form views or some kanban views."),
217 'image_small': fields.function(_get_image_resized, fnct_inv=_set_image_resized,
218 string="Smal-sized photo", type="binary", multi="_get_image_resized",
220 'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
222 help="Small-sized photo of the user. It is automatically "\
223 "resized as a 50x50px image, with aspect ratio keps. "\
224 "Use this field in form views or some kanban views."),
225 'active': fields.boolean('Active'),
226 'passport_id':fields.char('Passport No', size=64),
227 'color': fields.integer('Color Index'),
228 'city': fields.related('address_id', 'city', type='char', string='City'),
229 'login': fields.related('user_id', 'login', type='char', string='Login', readonly=1),
230 'last_login': fields.related('user_id', 'date', type='datetime', string='Latest Connection', readonly=1),
233 def unlink(self, cr, uid, ids, context=None):
234 resource_obj = self.pool.get('resource.resource')
236 for employee in self.browse(cr, uid, ids, context=context):
237 resource = employee.resource_id
239 resource_ids.append(resource.id)
241 resource_obj.unlink(cr, uid, resource_ids, context=context)
242 return super(hr_employee, self).unlink(cr, uid, ids, context=context)
244 def onchange_address_id(self, cr, uid, ids, address, context=None):
246 address = self.pool.get('res.partner').browse(cr, uid, address, context=context)
247 return {'value': {'work_email': address.email, 'work_phone': address.phone, 'mobile_phone': address.mobile}}
250 def onchange_company(self, cr, uid, ids, company, context=None):
253 company_id = self.pool.get('res.company').browse(cr, uid, company, context=context)
254 address = self.pool.get('res.partner').address_get(cr, uid, [company_id.partner_id.id], ['default'])
255 address_id = address and address['default'] or False
256 return {'value': {'address_id' : address_id}}
258 def onchange_department_id(self, cr, uid, ids, department_id, context=None):
259 value = {'parent_id': False}
261 department = self.pool.get('hr.department').browse(cr, uid, department_id)
262 value['parent_id'] = department.manager_id.id
263 return {'value': value}
265 def onchange_user(self, cr, uid, ids, user_id, context=None):
268 work_email = self.pool.get('res.users').browse(cr, uid, user_id, context=context).user_email
269 return {'value': {'work_email' : work_email}}
271 def _get_photo(self, cr, uid, context=None):
272 image_path = addons.get_module_resource('hr', 'images', 'photo.png')
273 return tools.resize_image_big(open(image_path, 'rb').read().encode('base64'))
277 'image_medium': _get_photo,
282 def _check_recursion(self, cr, uid, ids, context=None):
285 cr.execute('SELECT DISTINCT parent_id FROM hr_employee WHERE id IN %s AND parent_id!=id',(tuple(ids),))
286 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
293 (_check_recursion, 'Error ! You cannot create recursive Hierarchy of Employees.', ['parent_id']),
298 class hr_department(osv.osv):
299 _description = "Department"
300 _inherit = 'hr.department'
302 'manager_id': fields.many2one('hr.employee', 'Manager'),
303 'member_ids': fields.one2many('hr.employee', 'department_id', 'Members', readonly=True),
309 class res_users(osv.osv):
311 _inherit = 'res.users'
313 def create(self, cr, uid, data, context=None):
314 user_id = super(res_users, self).create(cr, uid, data, context=context)
316 # add shortcut unless 'noshortcut' is True in context
317 if not(context and context.get('noshortcut', False)):
318 data_obj = self.pool.get('ir.model.data')
320 data_id = data_obj._get_id(cr, uid, 'hr', 'ir_ui_view_sc_employee')
321 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
322 self.pool.get('ir.ui.view_sc').copy(cr, uid, view_id, default = {
323 'user_id': user_id}, context=context)
325 # Tolerate a missing shortcut. See product/product.py for similar code.
326 _logger.debug('Skipped meetings shortcut for user "%s"', data.get('name','<new'))
333 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: