[MERGE] forward port of branch saas-5 up to e4cb520
[odoo/odoo.git] / addons / hr_holidays / hr_holidays.py
1 # -*- coding: utf-8 -*-
2 ##################################################################################
3 #
4 # Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
5 # and 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 # $Id: hr.py 4656 2006-11-24 09:58:42Z Cyp $
8 #
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.
13 #
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.
18 #
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/>.
21 #
22 ##############################################################################
23
24 import datetime
25 import math
26 import time
27 from operator import attrgetter
28
29 from openerp.exceptions import Warning
30 from openerp import tools
31 from openerp.osv import fields, osv
32 from openerp.tools.translate import _
33
34
35 class hr_holidays_status(osv.osv):
36     _name = "hr.holidays.status"
37     _description = "Leave Type"
38
39     def get_days(self, cr, uid, ids, employee_id, context=None):
40         result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0,
41                                 virtual_remaining_leaves=0)) for id in ids)
42         holiday_ids = self.pool['hr.holidays'].search(cr, uid, [('employee_id', '=', employee_id),
43                                                                 ('state', 'in', ['confirm', 'validate1', 'validate']),
44                                                                 ('holiday_status_id', 'in', ids)
45                                                                 ], context=context)
46         for holiday in self.pool['hr.holidays'].browse(cr, uid, holiday_ids, context=context):
47             status_dict = result[holiday.holiday_status_id.id]
48             if holiday.type == 'add':
49                 status_dict['virtual_remaining_leaves'] += holiday.number_of_days
50                 if holiday.state == 'validate':
51                     status_dict['max_leaves'] += holiday.number_of_days
52                     status_dict['remaining_leaves'] += holiday.number_of_days
53             elif holiday.type == 'remove':  # number of days is negative
54                 status_dict['virtual_remaining_leaves'] += holiday.number_of_days
55                 if holiday.state == 'validate':
56                     status_dict['leaves_taken'] -= holiday.number_of_days
57                     status_dict['remaining_leaves'] += holiday.number_of_days
58         return result
59
60     def _user_left_days(self, cr, uid, ids, name, args, context=None):
61         employee_id = False
62         if context and 'employee_id' in context:
63             employee_id = context['employee_id']
64         else:
65             employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
66             if employee_ids:
67                 employee_id = employee_ids[0]
68         if employee_id:
69             res = self.get_days(cr, uid, ids, employee_id, context=context)
70         else:
71             res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
72         return res
73
74     _columns = {
75         'name': fields.char('Leave Type', size=64, required=True, translate=True),
76         'categ_id': fields.many2one('calendar.event.type', 'Meeting Type',
77             help='Once a leave is validated, Odoo will create a corresponding meeting of this type in the calendar.'),
78         'color_name': fields.selection([('red', 'Red'),('blue','Blue'), ('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 Department.'),
79         'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and will not take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
80         '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."),
81         'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
82         'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
83         'remaining_leaves': fields.function(_user_left_days, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
84         'virtual_remaining_leaves': fields.function(_user_left_days, string='Virtual Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval', multi='user_left_days'),
85         'double_validation': fields.boolean('Apply Double Validation', help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved."),
86     }
87     _defaults = {
88         'color_name': 'red',
89         'active': True,
90     }
91
92     def name_get(self, cr, uid, ids, context=None):
93
94         if not context.get('employee_id',False):
95             # leave counts is based on employee_id, would be inaccurate if not based on correct employee
96             return super(hr_holidays_status, self).name_get(cr, uid, ids, context=context)
97
98         res = []
99         for record in self.browse(cr, uid, ids, context=context):
100             name = record.name
101             if not record.limit:
102                 name = name + ('  (%g/%g)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0))
103             res.append((record.id, name))
104         return res
105
106
107 class hr_holidays(osv.osv):
108     _name = "hr.holidays"
109     _description = "Leave"
110     _order = "type desc, date_from asc"
111     _inherit = ['mail.thread', 'ir.needaction_mixin']
112     _track = {
113         'state': {
114             'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj.state == 'validate',
115             'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj.state == 'refuse',
116             'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj.state == 'confirm',
117         },
118     }
119
120     def _employee_get(self, cr, uid, context=None):        
121         emp_id = context.get('default_employee_id', False)
122         if emp_id:
123             return emp_id
124         ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
125         if ids:
126             return ids[0]
127         return False
128
129     def _compute_number_of_days(self, cr, uid, ids, name, args, context=None):
130         result = {}
131         for hol in self.browse(cr, uid, ids, context=context):
132             if hol.type=='remove':
133                 result[hol.id] = -hol.number_of_days_temp
134             else:
135                 result[hol.id] = hol.number_of_days_temp
136         return result
137
138     def _get_can_reset(self, cr, uid, ids, name, arg, context=None):
139         """User can reset a leave request if it is its own leave request or if
140         he is an Hr Manager. """
141         user = self.pool['res.users'].browse(cr, uid, uid, context=context)
142         group_hr_manager_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')[1]
143         if group_hr_manager_id in [g.id for g in user.groups_id]:
144             return dict.fromkeys(ids, True)
145         result = dict.fromkeys(ids, False)
146         for holiday in self.browse(cr, uid, ids, context=context):
147             if holiday.employee_id and holiday.employee_id.user_id and holiday.employee_id.user_id.id == uid:
148                 result[holiday.id] = True
149         return result
150
151     def _check_date(self, cr, uid, ids, context=None):
152         for holiday in self.browse(cr, uid, ids, context=context):
153             domain = [
154                 ('date_from', '<=', holiday.date_to),
155                 ('date_to', '>=', holiday.date_from),
156                 ('employee_id', '=', holiday.employee_id.id),
157                 ('id', '!=', holiday.id),
158                 ('state', 'not in', ['cancel', 'refuse']),
159             ]
160             nholidays = self.search_count(cr, uid, domain, context=context)
161             if nholidays:
162                 return False
163         return True
164
165     _check_holidays = lambda self, cr, uid, ids, context=None: self.check_holidays(cr, uid, ids, context=context)
166
167     _columns = {
168         'name': fields.char('Description', size=64),
169         'state': fields.selection([('draft', 'To Submit'), ('cancel', 'Cancelled'),('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved')],
170             'Status', readonly=True, track_visibility='onchange', copy=False,
171             help='The status is set to \'To Submit\', when a holiday request is created.\
172             \nThe status is \'To Approve\', when holiday request is confirmed by user.\
173             \nThe status is \'Refused\', when holiday request is refused by manager.\
174             \nThe status is \'Approved\', when holiday request is approved by manager.'),
175         'user_id':fields.related('employee_id', 'user_id', type='many2one', relation='res.users', string='User', store=True),
176         'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, select=True, copy=False),
177         'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, copy=False),
178         'holiday_status_id': fields.many2one("hr.holidays.status", "Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
179         'employee_id': fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
180         'manager_id': fields.many2one('hr.employee', 'First Approval', invisible=False, readonly=True, copy=False,
181                                       help='This area is automatically filled by the user who validate the leave'),
182         'notes': fields.text('Reasons',readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
183         'number_of_days_temp': fields.float('Allocation', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, copy=False),
184         'number_of_days': fields.function(_compute_number_of_days, string='Number of Days', store=True),
185         'meeting_id': fields.many2one('calendar.event', 'Meeting'),
186         'type': fields.selection([('remove','Leave Request'),('add','Allocation Request')], 'Request Type', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('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", select=True),
187         'parent_id': fields.many2one('hr.holidays', 'Parent'),
188         'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
189         'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
190         'category_id': fields.many2one('hr.employee.category', "Employee Tag", help='Category of Employee', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
191         'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Tag')], 'Allocation Mode', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category', required=True),
192         'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, copy=False,
193                                        help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
194         'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
195         'can_reset': fields.function(
196             _get_can_reset,
197             type='boolean'),
198     }
199     _defaults = {
200         'employee_id': _employee_get,
201         'state': 'confirm',
202         'type': 'remove',
203         'user_id': lambda obj, cr, uid, context: uid,
204         'holiday_type': 'employee'
205     }
206     _constraints = [
207         (_check_date, 'You can not have 2 leaves that overlaps on same day!', ['date_from','date_to']),
208         (_check_holidays, 'The number of remaining leaves is not sufficient for this leave type', ['state','number_of_days_temp'])
209     ] 
210     
211     _sql_constraints = [
212         ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", 
213          "The employee or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
214         ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."),
215         ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
216     ]
217
218     def _create_resource_leave(self, cr, uid, leaves, context=None):
219         '''This method will create entry in resource calendar leave object at the time of holidays validated '''
220         obj_res_leave = self.pool.get('resource.calendar.leaves')
221         for leave in leaves:
222             vals = {
223                 'name': leave.name,
224                 'date_from': leave.date_from,
225                 'holiday_id': leave.id,
226                 'date_to': leave.date_to,
227                 'resource_id': leave.employee_id.resource_id.id,
228                 'calendar_id': leave.employee_id.resource_id.calendar_id.id
229             }
230             obj_res_leave.create(cr, uid, vals, context=context)
231         return True
232
233     def _remove_resource_leave(self, cr, uid, ids, context=None):
234         '''This method will create entry in resource calendar leave object at the time of holidays cancel/removed'''
235         obj_res_leave = self.pool.get('resource.calendar.leaves')
236         leave_ids = obj_res_leave.search(cr, uid, [('holiday_id', 'in', ids)], context=context)
237         return obj_res_leave.unlink(cr, uid, leave_ids, context=context)
238
239     def onchange_type(self, cr, uid, ids, holiday_type, employee_id=False, context=None):
240         result = {}
241         if holiday_type == 'employee' and not employee_id:
242             ids_employee = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
243             if ids_employee:
244                 result['value'] = {
245                     'employee_id': ids_employee[0]
246                 }
247         elif holiday_type != 'employee':
248             result['value'] = {
249                     'employee_id': False
250                 }
251         return result
252
253     def onchange_employee(self, cr, uid, ids, employee_id):
254         result = {'value': {'department_id': False}}
255         if employee_id:
256             employee = self.pool.get('hr.employee').browse(cr, uid, employee_id)
257             result['value'] = {'department_id': employee.department_id.id}
258         return result
259
260     # TODO: can be improved using resource calendar method
261     def _get_number_of_days(self, date_from, date_to):
262         """Returns a float equals to the timedelta between two dates given as string."""
263
264         DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
265         from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
266         to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
267         timedelta = to_dt - from_dt
268         diff_day = timedelta.days + float(timedelta.seconds) / 86400
269         return diff_day
270
271     def unlink(self, cr, uid, ids, context=None):
272         for rec in self.browse(cr, uid, ids, context=context):
273             if rec.state not in ['draft', 'cancel', 'confirm']:
274                 raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is in %s state.')%(rec.state))
275         return super(hr_holidays, self).unlink(cr, uid, ids, context)
276
277     def onchange_date_from(self, cr, uid, ids, date_to, date_from):
278         """
279         If there are no date set for date_to, automatically set one 8 hours later than
280         the date_from.
281         Also update the number_of_days.
282         """
283         # date_to has to be greater than date_from
284         if (date_from and date_to) and (date_from > date_to):
285             raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
286
287         result = {'value': {}}
288
289         # No date_to set so far: automatically compute one 8 hours later
290         if date_from and not date_to:
291             date_to_with_delta = datetime.datetime.strptime(date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) + datetime.timedelta(hours=8)
292             result['value']['date_to'] = str(date_to_with_delta)
293
294         # Compute and update the number of days
295         if (date_to and date_from) and (date_from <= date_to):
296             diff_day = self._get_number_of_days(date_from, date_to)
297             result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
298         else:
299             result['value']['number_of_days_temp'] = 0
300
301         return result
302
303     def onchange_date_to(self, cr, uid, ids, date_to, date_from):
304         """
305         Update the number_of_days.
306         """
307
308         # date_to has to be greater than date_from
309         if (date_from and date_to) and (date_from > date_to):
310             raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
311
312         result = {'value': {}}
313
314         # Compute and update the number of days
315         if (date_to and date_from) and (date_from <= date_to):
316             diff_day = self._get_number_of_days(date_from, date_to)
317             result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
318         else:
319             result['value']['number_of_days_temp'] = 0
320
321         return result
322
323     def create(self, cr, uid, values, context=None):
324         """ Override to avoid automatic logging of creation """
325         if context is None:
326             context = {}
327         context = dict(context, mail_create_nolog=True)
328         hol_id = super(hr_holidays, self).create(cr, uid, values, context=context)
329         return hol_id
330
331     def holidays_reset(self, cr, uid, ids, context=None):
332         self.write(cr, uid, ids, {
333             'state': 'draft',
334             'manager_id': False,
335             'manager_id2': False,
336         })
337         to_unlink = []
338         for record in self.browse(cr, uid, ids, context=context):
339             for record2 in record.linked_request_ids:
340                 self.holidays_reset(cr, uid, [record2.id], context=context)
341                 to_unlink.append(record2.id)
342         if to_unlink:
343             self.unlink(cr, uid, to_unlink, context=context)
344         return True
345
346     def holidays_first_validate(self, cr, uid, ids, context=None):
347         obj_emp = self.pool.get('hr.employee')
348         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
349         manager = ids2 and ids2[0] or False
350         self.holidays_first_validate_notificate(cr, uid, ids, context=context)
351         return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
352
353     def holidays_validate(self, cr, uid, ids, context=None):
354         obj_emp = self.pool.get('hr.employee')
355         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
356         manager = ids2 and ids2[0] or False
357         self.write(cr, uid, ids, {'state':'validate'})
358         data_holiday = self.browse(cr, uid, ids)
359         for record in data_holiday:
360             if record.double_validation:
361                 self.write(cr, uid, [record.id], {'manager_id2': manager})
362             else:
363                 self.write(cr, uid, [record.id], {'manager_id': manager})
364             if record.holiday_type == 'employee' and record.type == 'remove':
365                 meeting_obj = self.pool.get('calendar.event')
366                 meeting_vals = {
367                     'name': record.name or _('Leave Request'),
368                     'categ_ids': record.holiday_status_id.categ_id and [(6,0,[record.holiday_status_id.categ_id.id])] or [],
369                     'duration': record.number_of_days_temp * 8,
370                     'description': record.notes,
371                     'user_id': record.user_id.id,
372                     'start': record.date_from,
373                     'stop': record.date_to,
374                     'allday': False,
375                     'state': 'open',            # to block that meeting date in the calendar
376                     'class': 'confidential'
377                 }   
378                 #Add the partner_id (if exist) as an attendee             
379                 if record.user_id and record.user_id.partner_id:
380                     meeting_vals['partner_ids'] = [(4,record.user_id.partner_id.id)]
381                     
382                 meeting_id = meeting_obj.create(cr, uid, meeting_vals)
383                 self._create_resource_leave(cr, uid, [record], context=context)
384                 self.write(cr, uid, ids, {'meeting_id': meeting_id})
385             elif record.holiday_type == 'category':
386                 emp_ids = obj_emp.search(cr, uid, [('category_ids', 'child_of', [record.category_id.id])])
387                 leave_ids = []
388                 for emp in obj_emp.browse(cr, uid, emp_ids):
389                     vals = {
390                         'name': record.name,
391                         'type': record.type,
392                         'holiday_type': 'employee',
393                         'holiday_status_id': record.holiday_status_id.id,
394                         'date_from': record.date_from,
395                         'date_to': record.date_to,
396                         'notes': record.notes,
397                         'number_of_days_temp': record.number_of_days_temp,
398                         'parent_id': record.id,
399                         'employee_id': emp.id
400                     }
401                     leave_ids.append(self.create(cr, uid, vals, context=None))
402                 for leave_id in leave_ids:
403                     # TODO is it necessary to interleave the calls?
404                     for sig in ('confirm', 'validate', 'second_validate'):
405                         self.signal_workflow(cr, uid, [leave_id], sig)
406         return True
407
408     def holidays_confirm(self, cr, uid, ids, context=None):
409         for record in self.browse(cr, uid, ids, context=context):
410             if record.employee_id and record.employee_id.parent_id and record.employee_id.parent_id.user_id:
411                 self.message_subscribe_users(cr, uid, [record.id], user_ids=[record.employee_id.parent_id.user_id.id], context=context)
412         return self.write(cr, uid, ids, {'state': 'confirm'})
413
414     def holidays_refuse(self, cr, uid, ids, context=None):
415         obj_emp = self.pool.get('hr.employee')
416         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
417         manager = ids2 and ids2[0] or False
418         for holiday in self.browse(cr, uid, ids, context=context):
419             if holiday.state == 'validate1':
420                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id': manager})
421             else:
422                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id2': manager})
423         self.holidays_cancel(cr, uid, ids, context=context)
424         return True
425
426     def holidays_cancel(self, cr, uid, ids, context=None):
427         meeting_obj = self.pool.get('calendar.event')
428         for record in self.browse(cr, uid, ids):
429             # Delete the meeting
430             if record.meeting_id:
431                 record.meeting_id.unlink()
432
433             # If a category that created several holidays, cancel all related
434             self.signal_workflow(cr, uid, map(attrgetter('id'), record.linked_request_ids or []), 'refuse')
435
436         self._remove_resource_leave(cr, uid, ids, context=context)
437         return True
438
439     def check_holidays(self, cr, uid, ids, context=None):
440         for record in self.browse(cr, uid, ids, context=context):
441             if record.holiday_type != 'employee' or record.type != 'remove' or not record.employee_id or record.holiday_status_id.limit:
442                 continue
443             leave_days = self.pool.get('hr.holidays.status').get_days(cr, uid, [record.holiday_status_id.id], record.employee_id.id, context=context)[record.holiday_status_id.id]
444             if leave_days['remaining_leaves'] < 0 or leave_days['virtual_remaining_leaves'] < 0:
445                 # Raising a warning gives a more user-friendly feedback than the default constraint error
446                 raise Warning(_('The number of remaining leaves is not sufficient for this leave type.\n'
447                                 'Please verify also the leaves waiting for validation.'))
448         return True
449
450     # -----------------------------
451     # OpenChatter and notifications
452     # -----------------------------
453
454     def _needaction_domain_get(self, cr, uid, context=None):
455         emp_obj = self.pool.get('hr.employee')
456         empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
457         dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
458         # if this user is a hr.manager, he should do second validations
459         if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
460             dom = ['|'] + dom + [('state', '=', 'validate1')]
461         return dom
462
463     def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
464         for obj in self.browse(cr, uid, ids, context=context):
465             self.message_post(cr, uid, [obj.id],
466                 _("Request approved, waiting second validation."), context=context)
467
468 class resource_calendar_leaves(osv.osv):
469     _inherit = "resource.calendar.leaves"
470     _description = "Leave Detail"
471     _columns = {
472         'holiday_id': fields.many2one("hr.holidays", "Leave Request"),
473     }
474
475
476
477 class hr_employee(osv.osv):
478     _inherit="hr.employee"
479
480     def create(self, cr, uid, vals, context=None):
481         # don't pass the value of remaining leave if it's 0 at the creation time, otherwise it will trigger the inverse
482         # function _set_remaining_days and the system may not be configured for. Note that we don't have this problem on
483         # the write because the clients only send the fields that have been modified.
484         if 'remaining_leaves' in vals and not vals['remaining_leaves']:
485             del(vals['remaining_leaves'])
486         return super(hr_employee, self).create(cr, uid, vals, context=context)
487
488     def _set_remaining_days(self, cr, uid, empl_id, name, value, arg, context=None):
489         employee = self.browse(cr, uid, empl_id, context=context)
490         diff = value - employee.remaining_leaves
491         type_obj = self.pool.get('hr.holidays.status')
492         holiday_obj = self.pool.get('hr.holidays')
493         # Find for holidays status
494         status_ids = type_obj.search(cr, uid, [('limit', '=', False)], context=context)
495         if len(status_ids) != 1 :
496             raise osv.except_osv(_('Warning!'),_("The feature behind the field 'Remaining Legal Leaves' can only be used when there is only one leave type with the option 'Allow to Override Limit' unchecked. (%s Found). Otherwise, the update is ambiguous as we cannot decide on which leave type the update has to be done. \nYou may prefer to use the classic menus 'Leave Requests' and 'Allocation Requests' located in 'Human Resources \ Leaves' to manage the leave days of the employees if the configuration does not allow to use this field.") % (len(status_ids)))
497         status_id = status_ids and status_ids[0] or False
498         if not status_id:
499             return False
500         if diff > 0:
501             leave_id = holiday_obj.create(cr, uid, {'name': _('Allocation for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status_id, 'type': 'add', 'holiday_type': 'employee', 'number_of_days_temp': diff}, context=context)
502         elif diff < 0:
503             raise osv.except_osv(_('Warning!'), _('You cannot reduce validated allocation requests'))
504         else:
505             return False
506         for sig in ('confirm', 'validate', 'second_validate'):
507             holiday_obj.signal_workflow(cr, uid, [leave_id], sig)
508         return True
509
510     def _get_remaining_days(self, cr, uid, ids, name, args, context=None):
511         cr.execute("""SELECT
512                 sum(h.number_of_days) as days,
513                 h.employee_id
514             from
515                 hr_holidays h
516                 join hr_holidays_status s on (s.id=h.holiday_status_id)
517             where
518                 h.state='validate' and
519                 s.limit=False and
520                 h.employee_id in %s
521             group by h.employee_id""", (tuple(ids),))
522         res = cr.dictfetchall()
523         remaining = {}
524         for r in res:
525             remaining[r['employee_id']] = r['days']
526         for employee_id in ids:
527             if not remaining.get(employee_id):
528                 remaining[employee_id] = 0.0
529         return remaining
530
531     def _get_leave_status(self, cr, uid, ids, name, args, context=None):
532         holidays_obj = self.pool.get('hr.holidays')
533         holidays_id = holidays_obj.search(cr, uid,
534            [('employee_id', 'in', ids), ('date_from','<=',time.strftime('%Y-%m-%d %H:%M:%S')),
535            ('date_to','>=',time.strftime('%Y-%m-%d 23:59:59')),('type','=','remove'),('state','not in',('cancel','refuse'))],
536            context=context)
537         result = {}
538         for id in ids:
539             result[id] = {
540                 'current_leave_state': False,
541                 'current_leave_id': False,
542                 'leave_date_from':False,
543                 'leave_date_to':False,
544             }
545         for holiday in self.pool.get('hr.holidays').browse(cr, uid, holidays_id, context=context):
546             result[holiday.employee_id.id]['leave_date_from'] = holiday.date_from
547             result[holiday.employee_id.id]['leave_date_to'] = holiday.date_to
548             result[holiday.employee_id.id]['current_leave_state'] = holiday.state
549             result[holiday.employee_id.id]['current_leave_id'] = holiday.holiday_status_id.id
550         return result
551
552     def _leaves_count(self, cr, uid, ids, field_name, arg, context=None):
553         Holidays = self.pool['hr.holidays']
554         return {
555             employee_id: Holidays.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context) 
556             for employee_id in ids
557         }
558
559     _columns = {
560         'remaining_leaves': fields.function(_get_remaining_days, string='Remaining Legal Leaves', fnct_inv=_set_remaining_days, type="float", help='Total number of legal leaves allocated to this employee, change this value to create allocation/leave request. Total based on all the leave types without overriding limit.'),
561         'current_leave_state': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
562             selection=[('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'),
563             ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
564         'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'),
565         'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'),
566         'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'),
567         'leaves_count': fields.function(_leaves_count, type='integer', string='Leaves'),
568
569     }
570
571
572 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: