1 # -*- coding: utf-8 -*-
2 ##################################################################################
4 # Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
5 # and 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # $Id: hr.py 4656 2006-11-24 09:58:42Z Cyp $
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU Affero General Public License as
11 # published by the Free Software Foundation, either version 3 of the
12 # License, or (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU Affero General Public License for more details.
19 # You should have received a copy of the GNU Affero General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 ##############################################################################
28 from osv import fields, osv
29 from tools.translate import _
31 class hr_holidays_status(osv.osv):
32 _name = "hr.holidays.status"
33 _description = "Leave Type"
35 def get_days_cat(self, cr, uid, ids, category_id, return_false, context=None):
39 for record in self.browse(cr, uid, ids, context=context):
41 max_leaves = leaves_taken = 0
43 cr.execute("""SELECT type, sum(number_of_days) FROM hr_holidays WHERE category_id = %s AND state='validate' AND holiday_status_id = %s GROUP BY type""", (str(category_id), str(record.id)))
44 for line in cr.fetchall():
45 if line[0] =='remove':
46 leaves_taken = -line[1]
49 res[record.id]['max_leaves'] = max_leaves
50 res[record.id]['leaves_taken'] = leaves_taken
51 res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
54 def get_days(self, cr, uid, ids, employee_id, return_false, context=None):
58 for record in self.browse(cr, uid, ids, context=context):
60 max_leaves = leaves_taken = 0
62 cr.execute("""SELECT type, sum(number_of_days) FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id = %s GROUP BY type""", (str(employee_id), str(record.id)))
63 for line in cr.fetchall():
64 if line[0] =='remove':
65 leaves_taken = -line[1]
68 res[record.id]['max_leaves'] = max_leaves
69 res[record.id]['leaves_taken'] = leaves_taken
70 res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
73 def _user_left_days(self, cr, uid, ids, name, args, context=None):
80 if context and context.has_key('employee_id'):
81 if not context['employee_id']:
83 employee_id = context['employee_id']
85 employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
87 employee_id = employee_ids[0]
91 res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
93 res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
96 # To do: we can add remaining_leaves_category field to display remaining leaves for particular type
98 'name': fields.char('Name', size=64, required=True, translate=True),
99 'categ_id': fields.many2one('crm.case.categ', 'Meeting Category', domain="[('object_id.model', '=', 'crm.meeting')]", help='If you link this type of leave with a category in the CRM, it will synchronize each leave asked with a case in this category, to display it in the company shared calendar for example.'),
100 'color_name': fields.selection([('red', 'Red'), ('lightgreen', 'Light Green'), ('lightblue','Light Blue'), ('lightyellow', 'Light Yellow'), ('magenta', 'Magenta'),('lightcyan', 'Light Cyan'),('black', 'Black'),('lightpink', 'Light Pink'),('brown', 'Brown'),('violet', 'Violet'),('lightcoral', 'Light Coral'),('lightsalmon', 'Light Salmon'),('lavender', 'Lavender'),('wheat', 'Wheat'),('ivory', 'Ivory')],'Color in Report', required=True, help='This color will be used in the leaves summary located in Reporting\Leaves by Departement'),
101 'limit': fields.boolean('Allow to Override Limit', help='If you tick this checkbox, the system will allow, for this section, the employees to take more leaves than the available ones.'),
102 'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the leave type without removing it."),
103 'max_leaves': fields.function(_user_left_days, method=True, string='Maximum Leaves Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
104 'leaves_taken': fields.function(_user_left_days, method=True, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
105 'remaining_leaves': fields.function(_user_left_days, method=True, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
106 'double_validation': fields.boolean('Apply Double Validation', help="If its True then its Allocation/Request have to be validated by second validator")
115 class hr_holidays(osv.osv):
116 _name = "hr.holidays"
117 _description = "Leave"
118 _order = "type desc, date_from asc"
120 def _employee_get(obj, cr, uid, context=None):
123 ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
129 'name': fields.char('Description', required=True, readonly=True, size=64, states={'draft':[('readonly',False)]}),
130 'state': fields.selection([('draft', 'Draft'), ('confirm', 'Waiting Validation'), ('refuse', 'Refused'), ('validate1', 'Waiting Second Validation'), ('validate', 'Validated'), ('cancel', 'Cancelled')], 'State', readonly=True, help='When the holiday request is created the state is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the state is \'Waiting Validation\'.\
131 If the admin accepts it, the state is \'Validated\'. If it is refused, the state is \'Refused\'.'),
132 'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)]}),
133 'user_id':fields.many2one('res.users', 'User', states={'draft':[('readonly',False)]}, select=True, readonly=True),
134 'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)]}),
135 'holiday_status_id' : fields.many2one("hr.holidays.status", " Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)]}),
136 'employee_id' : fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)]}, help='Leave Manager can let this field empty if this leave request/allocation is for every employee'),
137 'manager_id': fields.many2one('hr.employee', 'Leave Manager', invisible=False, readonly=True, help='This area is automaticly filled by the user who validate the leave'),
138 'notes': fields.text('Notes',readonly=True, states={'draft':[('readonly',False)]}),
139 'number_of_days': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
140 'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
141 'case_id': fields.many2one('crm.meeting', 'Case'),
142 'type': fields.selection([('remove','Leave Request'),('add','Allocation Request')], 'Request Type', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="Choose 'Leave Request' if someone wants to take an off-day. \nChoose 'Allocation Request' if you want to increase the number of leaves available for someone"),
143 'allocation_type': fields.selection([('employee','Employee Request'),('company','Company Allocation')], 'Allocation Type', required=True, readonly=True, states={'draft':[('readonly',False)]}, help='This field is only for informative purposes, to depict if the leave request/allocation comes from an employee or from the company'),
144 'parent_id': fields.many2one('hr.holidays', 'Parent'),
145 'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
146 'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
147 'category_id': fields.many2one('hr.employee.category', "Category", help='Category Of employee'),
148 'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Category')], 'Holiday Type', help='By Employee: Allocation/Request for individual Employee, By Employee Category: Allocation/Request for group of employees in category'),
149 'manager_id2': fields.many2one('hr.employee', 'Second Validator', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
153 'employee_id': _employee_get ,
156 'allocation_type': 'employee',
157 'user_id': lambda obj, cr, uid, context: uid,
158 'holiday_type': 'employee'
161 def _create_resource_leave(self, cr, uid, vals, context=None):
162 '''This method will create entry in resource calendar leave object at the time of holidays validated '''
165 obj_res_leave = self.pool.get('resource.calendar.leaves')
166 return obj_res_leave.create(cr, uid, vals, context=context)
168 def _remove_resouce_leave(self, cr, uid, ids, context=None):
169 '''This method will create entry in resource calendar leave object at the time of holidays cancel/removed'''
170 obj_res_leave = self.pool.get('resource.calendar.leaves')
173 leave_ids = obj_res_leave.search(cr, uid, [('holiday_id', 'in', ids)], context=context)
174 return obj_res_leave.unlink(cr, uid, leave_ids)
176 def create(self, cr, uid, vals, context=None):
179 if 'holiday_type' in vals:
180 if vals['holiday_type'] == 'employee':
181 vals.update({'category_id': False})
183 vals.update({'employee_id': False})
184 if context.has_key('type'):
185 vals['type'] = context['type']
186 if context.has_key('allocation_type'):
187 vals['allocation_type'] = context['allocation_type']
188 return super(hr_holidays, self).create(cr, uid, vals, context=context)
190 def write(self, cr, uid, ids, vals, context=None):
193 if 'holiday_type' in vals:
194 if vals['holiday_type'] == 'employee':
195 vals.update({'category_id': False})
197 vals.update({'employee_id': False})
198 return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
200 def onchange_type(self, cr, uid, ids, holiday_type):
202 if holiday_type=='employee':
203 ids_employee = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
206 'employee_id': ids_employee[0]
210 def _get_number_of_days(self, date_from, date_to):
211 """Returns a float equals to the timedelta between two dates given as string."""
213 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
214 from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
215 to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
216 timedelta = to_dt - from_dt
217 diff_day = timedelta.days + float(timedelta.seconds) / 86400
220 def _update_user_holidays(self, cr, uid, ids):
221 for record in self.browse(cr, uid, ids):
222 if record.state=='validate':
224 self.pool.get('crm.meeting').unlink(cr, uid, [record.case_id.id])
225 if record.linked_request_ids:
227 [list_ids.append(i) for id in record.linked_request_ids]
228 self.holidays_cancel(cr, uid, list_ids)
229 self.unlink(cr, uid, list_ids)
231 def _check_date(self, cr, uid, ids):
232 for rec in self.read(cr, uid, ids, ['number_of_days_temp', 'date_from','date_to', 'type']):
233 if rec['number_of_days_temp'] < 0:
235 if rec['type'] == 'add':
237 date_from = time.strptime(rec['date_from'], '%Y-%m-%d %H:%M:%S')
238 date_to = time.strptime(rec['date_to'], '%Y-%m-%d %H:%M:%S')
239 if date_from > date_to:
243 _constraints = [(_check_date, 'Start date should not be larger than end date!\nNumber of Days should be greater than 1!', ['number_of_days_temp'])]
245 def unlink(self, cr, uid, ids, context=None):
248 self._update_user_holidays(cr, uid, ids)
249 self._remove_resouce_leave(cr, uid, ids, context=context)
250 return super(hr_holidays, self).unlink(cr, uid, ids, context)
252 def onchange_date_from(self, cr, uid, ids, date_to, date_from):
254 if date_to and date_from:
255 diff_day = self._get_number_of_days(date_from, date_to)
257 'number_of_days_temp': round(diff_day)+1
261 'number_of_days_temp': 0,
265 def onchange_date_to(self, cr, uid, ids, date_from, date_to):
266 return self.onchange_date_from(cr, uid, ids, date_to, date_from)
268 def onchange_sec_id(self, cr, uid, ids, status, context=None):
273 brows_obj = self.pool.get('hr.holidays.status').browse(cr, uid, [status], context=context)[0]
274 if brows_obj.categ_id and brows_obj.categ_id.section_id and not brows_obj.categ_id.section_id.allow_unlink:
276 'title': "Warning for ",
277 'message': "You won\'t be able to cancel this leave request because the CRM Sales Team of the leave type disallows."
279 return {'warning': warning}
281 def set_to_draft(self, cr, uid, ids, *args):
282 wf_service = netsvc.LocalService("workflow")
283 self.write(cr, uid, ids, {
289 wf_service.trg_create(uid, 'hr.holidays', id, cr)
292 def holidays_validate2(self, cr, uid, ids, *args):
293 vals = {'state':'validate1'}
294 self.check_holidays(cr, uid, ids)
295 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)])
297 vals['manager_id'] = ids2[0]
299 raise osv.except_osv(_('Warning !'),_('No user related to the selected employee.'))
300 self.write(cr, uid, ids, vals)
303 def holidays_validate(self, cr, uid, ids, *args):
304 obj_emp = self.pool.get('hr.employee')
305 data_holiday = self.browse(cr, uid, ids)
306 self.check_holidays(cr, uid, ids)
307 vals = {'state':'validate'}
308 ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
310 if data_holiday[0].state == 'validate1':
311 vals['manager_id2'] = ids2[0]
313 vals['manager_id'] = ids2[0]
315 raise osv.except_osv(_('Warning !'), _('No user related to the selected employee.'))
316 self.write(cr, uid, ids, vals)
317 for record in data_holiday:
318 if record.holiday_type == 'employee' and record.type == 'remove':
321 'date_from': record.date_from,
322 'date_to': record.date_to,
323 'calendar_id': record.employee_id.calendar_id.id,
324 'company_id': record.employee_id.company_id.id,
325 'resource_id': record.employee_id.resource_id.id,
326 'holiday_id': record.id
328 self._create_resource_leave(cr, uid, vals)
329 elif record.holiday_type == 'category' and record.type == 'remove':
330 emp_ids = obj_emp.search(cr, uid, [('category_ids', '=', record.category_id.id)])
331 for emp in obj_emp.browse(cr, uid, emp_ids):
334 'date_from': record.date_from,
335 'date_to': record.date_to,
336 'calendar_id': emp.calendar_id.id,
337 'company_id': emp.company_id.id,
338 'resource_id': emp.resource_id.id,
339 'holiday_id':record.id
341 self._create_resource_leave(cr, uid, vals)
344 def holidays_confirm(self, cr, uid, ids, *args):
345 for record in self.browse(cr, uid, ids):
347 leave_asked = record.number_of_days_temp
348 if record.holiday_type == 'employee' and record.type == 'remove':
349 if record.employee_id and not record.holiday_status_id.limit:
350 leaves_rest = self.pool.get('hr.holidays.status').get_days( cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
351 if leaves_rest < leave_asked:
352 raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.employee_id.name)))
353 nb = -(record.number_of_days_temp)
354 elif record.holiday_type == 'category' and record.type == 'remove':
355 if record.category_id and not record.holiday_status_id.limit:
356 leaves_rest = self.pool.get('hr.holidays.status').get_days_cat( cr, uid, [record.holiday_status_id.id], record.category_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
357 if leaves_rest < leave_asked:
358 raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.category_id.name)))
359 nb = -(record.number_of_days_temp)
361 nb = record.number_of_days_temp
363 if record.holiday_type == 'employee' and record.employee_id:
364 user_id = record.employee_id.user_id and record.employee_id.user_id.id or uid
366 self.write(cr, uid, [record.id], {'state':'confirm', 'number_of_days': nb, 'user_id': user_id })
369 def holidays_refuse(self, cr, uid, ids, *args):
370 vals = {'state': 'refuse'}
371 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
373 vals['manager_id'] = ids2[0]
374 self.write(cr, uid, ids, vals)
377 def holidays_cancel(self, cr, uid, ids, *args):
378 self._update_user_holidays(cr, uid, ids)
379 self.write(cr, uid, ids, {'state': 'cancel'})
380 self._remove_resouce_leave(cr, uid, ids)
383 def holidays_draft(self, cr, uid, ids, *args):
384 self.write(cr, uid, ids, {'state': 'draft'})
387 def check_holidays(self, cr, uid, ids):
388 holi_status_obj = self.pool.get('hr.holidays.status')
389 emp_obj = self.pool.get('hr.employee')
390 meeting_obj = self.pool.get('crm.meeting')
391 for record in self.browse(cr, uid, ids):
392 if not record.number_of_days:
393 raise osv.except_osv(_('Warning!'), _('Wrong leave definition.'))
394 if record.holiday_type=='employee' and record.employee_id:
395 leave_asked = record.number_of_days
396 if leave_asked < 0.00:
397 if not record.holiday_status_id.limit:
398 leaves_rest = holi_status_obj.get_days(cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
399 if leaves_rest < -(leave_asked):
400 raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
401 elif record.holiday_type == 'category' and record.category_id:
402 leave_asked = record.number_of_days
403 if leave_asked < 0.00:
404 if not record.holiday_status_id.limit:
405 leaves_rest = holi_status_obj.get_days_cat(cr, uid, [record.holiday_status_id.id], record.category_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
406 if leaves_rest < -(leave_asked):
407 raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
408 else:# This condition will never meet!!
411 'name' : record.name,
412 'holiday_status_id' : record.holiday_status_id.id,
414 'date_from' : record.date_from,
415 'date_to' : record.date_to,
416 'notes' : record.notes,
417 'number_of_days': record.number_of_days,
418 'number_of_days_temp': record.number_of_days_temp,
420 'allocation_type': record.allocation_type,
421 'parent_id': record.id,
423 employee_ids = emp_obj.search(cr, uid, [])
424 for employee in employee_ids:
425 vals['employee_id'] = employee
426 user_id = emp_obj.search(cr, uid, [('user_id','=',uid)])
428 vals['user_id'] = user_id[0]
429 holiday_ids.append(self.create(cr, uid, vals, context=None))
430 self.holidays_confirm(cr, uid, holiday_ids)
431 self.holidays_validate(cr, uid, holiday_ids)
433 #if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
434 # if record.holiday_status_id.categ_id and record.date_from and record.date_to:
436 # vals['name']=record.name
437 # vals['categ_id']=record.holiday_status_id.categ_id.id
438 # epoch_c = time.mktime(time.strptime(record.date_to,'%Y-%m-%d %H:%M:%S'))
439 # epoch_d = time.mktime(time.strptime(record.date_from,'%Y-%m-%d %H:%M:%S'))
440 # diff_day = (epoch_c - epoch_d)/(3600*24)
441 # vals['duration'] = (diff_day) * 8
442 # vals['note'] = record.notes
443 ## vals['user_id'] = record.user_id.id
444 # vals['date'] = record.date_from
445 # if record.holiday_type=='employee':
446 # vals['user_id'] = record.user_id.id
447 if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
448 diff_day = self._get_number_of_days(record.date_from, record.date_to)
450 'name' : record.name,
451 'categ_id' : record.holiday_status_id.categ_id.id,
452 'duration' : (diff_day) * 8,
453 'note' : record.notes,
454 'user_id' : record.user_id.id,
455 'date' : record.date_from,
457 case_id = meeting_obj.create(cr, uid, vals)
458 self.write(cr, uid, ids, {'case_id': case_id})
464 class resource_calendar_leaves(osv.osv):
465 _inherit = "resource.calendar.leaves"
466 _description = "Leave Detail"
468 'holiday_id': fields.many2one("hr.holidays", "Holiday"),
471 resource_calendar_leaves()
473 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: