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 ##############################################################################
27 from osv import fields, osv
28 from tools.translate import _
30 class hr_holidays_status(osv.osv):
31 _name = "hr.holidays.status"
32 _description = "Leave Type"
34 def get_days_cat(self, cr, uid, ids, category_id, return_false, context={}):
36 for record in self.browse(cr, uid, ids, context):
38 max_leaves = leaves_taken = 0
40 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)))
41 for line in cr.fetchall():
42 if line[0] =='remove':
43 leaves_taken = -line[1]
46 res[record.id]['max_leaves'] = max_leaves
47 res[record.id]['leaves_taken'] = leaves_taken
48 res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
51 def get_days(self, cr, uid, ids, employee_id, return_false, context={}):
53 for record in self.browse(cr, uid, ids, context=context):
55 max_leaves = leaves_taken = 0
57 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)))
58 for line in cr.fetchall():
59 if line[0] =='remove':
60 leaves_taken = -line[1]
63 res[record.id]['max_leaves'] = max_leaves
64 res[record.id]['leaves_taken'] = leaves_taken
65 res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
68 def _user_left_days(self, cr, uid, ids, name, args, context={}):
72 if context and context.has_key('employee_id'):
73 if not context['employee_id']:
75 employee_id = context['employee_id']
77 employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)])
79 employee_id = employee_ids[0]
83 res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
85 res = dict.fromkeys(ids, {name: 0})
88 # To do: we can add remaining_leaves_category field to display remaining leaves for particular type
90 'name': fields.char('Name', size=64, required=True, translate=True),
91 '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.'),
92 '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'),
93 'limit': fields.boolean('Allow to Override Limit', help='If you thick this checkbox, the system will allow, for this section, the employees to take more leaves than the available ones.'),
94 '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."),
95 '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'),
96 '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'),
97 'remaining_leaves': fields.function(_user_left_days, method=True, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
98 'double_validation': fields.boolean('Apply Double Validation', help="If its True then its Allocation/Request have to be validated by second validator")
107 class hr_holidays(osv.osv):
108 _name = "hr.holidays"
109 _description = "Leave"
110 _order = "type desc, date_from asc"
112 def _employee_get(obj, cr, uid, context=None):
113 ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
119 'name' : fields.char('Description', required=True, readonly=True, size=64, states={'draft':[('readonly',False)]}),
120 '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\'.\
121 If the admin accepts it, the state is \'Validated\'. If it is refused, the state is \'Refused\'.'),
122 'date_from' : fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)]}),
123 'user_id':fields.many2one('res.users', 'User', states={'draft':[('readonly',False)]}, select=True, readonly=True),
124 'date_to' : fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)]}),
125 'holiday_status_id' : fields.many2one("hr.holidays.status", "Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)]}),
126 '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'),
127 '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'),
128 'notes' : fields.text('Notes',readonly=True, states={'draft':[('readonly',False)]}),
129 'number_of_days': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
130 'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
131 'case_id': fields.many2one('crm.meeting', 'Case'),
132 '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"),
133 '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'),
134 'parent_id': fields.many2one('hr.holidays', 'Parent'),
135 'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
136 'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
137 'category_id': fields.many2one('hr.employee.category', "Category", help='Category Of employee'),
138 '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'),
139 '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)')
143 'employee_id' : _employee_get ,
146 'allocation_type': 'employee',
147 'user_id': lambda obj, cr, uid, context: uid,
148 'holiday_type': 'employee'
151 def create(self, cr, uid, vals, context=None):
154 if 'holiday_type' in vals:
155 if vals['holiday_type'] == 'employee':
156 vals.update({'category_id': False})
158 vals.update({'employee_id': False})
159 if context.has_key('type'):
160 vals['type'] = context['type']
161 if context.has_key('allocation_type'):
162 vals['allocation_type'] = context['allocation_type']
163 return super(hr_holidays, self).create(cr, uid, vals, context=context)
165 def write(self, cr, uid, ids, vals, context=None):
168 if 'holiday_type' in vals:
169 if vals['holiday_type'] == 'employee':
170 vals.update({'category_id': False})
172 vals.update({'employee_id': False})
173 return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
175 def onchange_type(self, cr, uid, ids, holiday_type):
177 if holiday_type=='employee':
178 ids_employee = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
181 'employee_id': ids_employee[0]
185 def _get_number_of_days(self, date_from, date_to):
186 """Returns a float equals to the timedelta between two dates given as string."""
188 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
189 from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
190 to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
191 timedelta = to_dt - from_dt
192 diff_day = timedelta.days + float(timedelta.seconds) / 86400
195 def _update_user_holidays(self, cr, uid, ids):
196 for record in self.browse(cr, uid, ids):
197 if record.state=='validate':
199 self.pool.get('crm.meeting').unlink(cr,uid,[record.case_id.id])
200 if record.linked_request_ids:
202 [list_ids.append(i) for id in record.linked_request_ids]
203 self.holidays_cancel(cr, uid, list_ids)
204 self.unlink(cr, uid, list_ids)
206 def _check_date(self, cr, uid, ids):
207 for rec in self.read(cr, uid, ids, ['number_of_days_temp','date_from','date_to', 'type']):
208 if rec['number_of_days_temp'] < 0:
210 if rec['type']=='add':
212 date_from = time.strptime(rec['date_from'], '%Y-%m-%d %H:%M:%S')
213 date_to = time.strptime(rec['date_to'], '%Y-%m-%d %H:%M:%S')
214 if date_from > date_to:
218 _constraints = [(_check_date, 'Start date should not be larger than end date!\nNumber of Days should be greater than 1!', ['number_of_days_temp'])]
220 def unlink(self, cr, uid, ids, context={}):
221 leave_obj = self.pool.get('resource.calendar.leaves')
222 self._update_user_holidays(cr, uid, ids)
223 leave_ids = leave_obj.search(cr, uid, [('holiday_id', 'in', ids)])
224 leave_obj.unlink(cr, uid, leave_ids)
225 return super(hr_holidays, self).unlink(cr, uid, ids, context)
228 def onchange_date_from(self, cr, uid, ids, date_to, date_from):
230 if date_to and date_from:
231 diff_day = self._get_number_of_days(date_from, date_to)
233 'number_of_days_temp': round(diff_day)+1
237 'number_of_days_temp': 0,
241 def onchange_date_to(self, cr, uid, ids, date_from, date_to):
242 return self.onchange_date_from(cr, uid, ids, date_to, date_from)
244 def onchange_sec_id(self, cr, uid, ids, status, context={}):
247 brows_obj = self.pool.get('hr.holidays.status').browse(cr, uid, [status])[0]
248 if brows_obj.categ_id and brows_obj.categ_id.section_id and not brows_obj.categ_id.section_id.allow_unlink:
250 'title': "Warning for ",
251 'message': "You won\'t be able to cancel this leave request because the CRM Sales Team of the leave type disallows."
253 return {'warning': warning}
255 def set_to_draft(self, cr, uid, ids, *args):
256 self.write(cr, uid, ids, {
261 wf_service = netsvc.LocalService("workflow")
262 for holiday_id in ids:
263 wf_service.trg_create(uid, 'hr.holidays', holiday_id, cr)
266 def holidays_validate2(self, cr, uid, ids, *args):
267 vals = {'state':'validate1'}
268 self.check_holidays(cr, uid, ids)
269 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
271 vals['manager_id'] = ids2[0]
273 raise osv.except_osv(_('Warning !'),_('No user related to the selected employee.'))
274 self.write(cr, uid, ids, vals)
277 def holidays_validate(self, cr, uid, ids, *args):
278 obj_res_leave = self.pool.get('resource.calendar.leaves')
279 obj_emp = self.pool.get('hr.employee')
280 data_holiday = self.browse(cr, uid, ids)
281 self.check_holidays(cr, uid, ids)
282 vals = {'state':'validate'}
283 ids2 = obj_emp.search(cr, uid, [('user_id','=', uid)])
285 if data_holiday[0].state == 'validate1':
286 vals['manager_id2'] = ids2[0]
288 vals['manager_id'] = ids2[0]
290 raise osv.except_osv(_('Warning !'),_('No user related to the selected employee.'))
291 self.write(cr, uid, ids, vals)
292 for record in data_holiday:
293 if record.holiday_type=='employee' and record.type=='remove':
296 'date_from': record.date_from,
297 'date_to': record.date_to,
298 'calendar_id': record.employee_id.calendar_id.id,
299 'company_id': record.employee_id.company_id.id,
300 'resource_id': record.employee_id.resource_id.id,
301 'holiday_id': record.id
303 obj_res_leave.create(cr, uid, vals)
304 elif record.holiday_type=='category' and record.type=='remove':
305 emp_ids = obj_emp.search(cr, uid, [('category_id', '=', record.category_id.id)])
306 for emp in obj_emp.browse(cr, uid, emp_ids):
309 'date_from': record.date_from,
310 'date_to': record.date_to,
311 'calendar_id': emp.calendar_id.id,
312 'company_id': emp.company_id.id,
313 'resource_id': emp.resource_id.id,
314 'holiday_id':record.id
316 obj_res_leave.create(cr, uid, vals)
319 def holidays_confirm(self, cr, uid, ids, *args):
320 for record in self.browse(cr, uid, ids):
322 leave_asked = record.number_of_days_temp
323 if record.holiday_type=='employee' and record.type == 'remove':
324 if record.employee_id and not record.holiday_status_id.limit:
325 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']
326 if leaves_rest < leave_asked:
327 raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.employee_id.name)))
328 nb = -(record.number_of_days_temp)
329 elif record.holiday_type=='category' and record.type == 'remove':
330 if record.category_id and not record.holiday_status_id.limit:
331 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']
332 if leaves_rest < leave_asked:
333 raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.category_id.name)))
334 nb = -(record.number_of_days_temp)
336 nb = record.number_of_days_temp
338 if record.holiday_type=='employee' and record.employee_id:
339 user_id = record.employee_id.user_id and record.employee_id.user_id.id or uid
341 self.write(cr, uid, [record.id], {
343 'number_of_days': nb,
348 def holidays_refuse(self, cr, uid, ids, *args):
352 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
354 vals['manager_id'] = ids2[0]
355 self.write(cr, uid, ids, vals)
358 def holidays_cancel(self, cr, uid, ids, *args):
359 leave_obj = self.pool.get('resource.calendar.leaves')
360 self._update_user_holidays(cr, uid, ids)
361 self.write(cr, uid, ids, {
364 leave_ids = leave_obj.search(cr, uid, [('holiday_id', 'in', ids)])
365 leave_obj.unlink(cr, uid, leave_ids)
368 def holidays_draft(self, cr, uid, ids, *args):
369 self.write(cr, uid, ids, {
372 self.pool.get('resource.calendar.leaves')
375 def check_holidays(self, cr, uid, ids):
376 for record in self.browse(cr, uid, ids):
377 if not record.number_of_days:
378 raise osv.except_osv(_('Warning!'),_('Wrong leave definition.'))
379 if record.holiday_type=='employee' and record.employee_id:
380 leave_asked = record.number_of_days
381 if leave_asked < 0.00:
382 if not record.holiday_status_id.limit:
383 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']
384 if leaves_rest < -(leave_asked):
385 raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
386 elif record.holiday_type=='category' and record.category_id:
387 leave_asked = record.number_of_days
388 if leave_asked < 0.00:
389 if not record.holiday_status_id.limit:
390 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']
391 if leaves_rest < -(leave_asked):
392 raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
393 else:# This condition will never meet!!
396 'name' : record.name,
397 'holiday_status_id' : record.holiday_status_id.id,
399 'date_from' : record.date_from,
400 'date_to' : record.date_to,
401 'notes' : record.notes,
402 'number_of_days': record.number_of_days,
403 'number_of_days_temp': record.number_of_days_temp,
405 'allocation_type': record.allocation_type,
406 'parent_id': record.id,
408 employee_ids = self.pool.get('hr.employee').search(cr, uid, [])
409 for employee in employee_ids:
410 vals['employee_id'] = employee
411 user_id = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)])
413 vals['user_id'] = user_id[0]
414 holiday_ids.append(self.create(cr, uid, vals, context={}))
415 self.holidays_confirm(cr, uid, holiday_ids)
416 self.holidays_validate(cr, uid, holiday_ids)
418 #if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
419 # if record.holiday_status_id.categ_id and record.date_from and record.date_to:
421 # vals['name']=record.name
422 # vals['categ_id']=record.holiday_status_id.categ_id.id
423 # epoch_c = time.mktime(time.strptime(record.date_to,'%Y-%m-%d %H:%M:%S'))
424 # epoch_d = time.mktime(time.strptime(record.date_from,'%Y-%m-%d %H:%M:%S'))
425 # diff_day = (epoch_c - epoch_d)/(3600*24)
426 # vals['duration'] = (diff_day) * 8
427 # vals['note'] = record.notes
428 ## vals['user_id'] = record.user_id.id
429 # vals['date'] = record.date_from
430 # if record.holiday_type=='employee':
431 # vals['user_id'] = record.user_id.id
432 if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
433 diff_day = self._get_number_of_days(record.date_from, record.date_to)
435 'name' : record.name,
436 'categ_id' : record.holiday_status_id.categ_id.id,
437 'duration' : (diff_day) * 8,
438 'note' : record.notes,
439 'user_id' : record.user_id.id,
440 'date' : record.date_from,
442 case_id = self.pool.get('crm.meeting').create(cr,uid,vals)
443 self.write(cr, uid, ids, {'case_id':case_id})
447 class resource_calendar_leaves(osv.osv):
448 _inherit = "resource.calendar.leaves"
449 _description = "Leave Detail"
451 'holiday_id': fields.many2one("hr.holidays", "Holiday"),
454 resource_calendar_leaves()
455 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: