[FIX] [CLEAN] Various: fixed / cleaned use of dict.fromkeys.
[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((res_id, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0}) for res_id in ids)
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         if values.get('state') and values['state'] not in ['draft', 'confirm', 'cancel'] and not self.pool['res.users'].has_group(cr, uid, 'base.group_hr_user'):
329             raise osv.except_osv(_('Warning!'), _('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state'))
330         return super(hr_holidays, self).create(cr, uid, values, context=context)
331
332     def write(self, cr, uid, ids, vals, context=None):
333         if vals.get('state') and vals['state'] not in ['draft', 'confirm', 'cancel'] and not self.pool['res.users'].has_group(cr, uid, 'base.group_hr_user'):
334             raise osv.except_osv(_('Warning!'), _('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % vals.get('state'))
335         return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
336
337     def holidays_reset(self, cr, uid, ids, context=None):
338         self.write(cr, uid, ids, {
339             'state': 'draft',
340             'manager_id': False,
341             'manager_id2': False,
342         })
343         to_unlink = []
344         for record in self.browse(cr, uid, ids, context=context):
345             for record2 in record.linked_request_ids:
346                 self.holidays_reset(cr, uid, [record2.id], context=context)
347                 to_unlink.append(record2.id)
348         if to_unlink:
349             self.unlink(cr, uid, to_unlink, context=context)
350         return True
351
352     def holidays_first_validate(self, cr, uid, ids, context=None):
353         obj_emp = self.pool.get('hr.employee')
354         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
355         manager = ids2 and ids2[0] or False
356         self.holidays_first_validate_notificate(cr, uid, ids, context=context)
357         return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
358
359     def holidays_validate(self, cr, uid, ids, context=None):
360         obj_emp = self.pool.get('hr.employee')
361         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
362         manager = ids2 and ids2[0] or False
363         self.write(cr, uid, ids, {'state':'validate'})
364         data_holiday = self.browse(cr, uid, ids)
365         for record in data_holiday:
366             if record.double_validation:
367                 self.write(cr, uid, [record.id], {'manager_id2': manager})
368             else:
369                 self.write(cr, uid, [record.id], {'manager_id': manager})
370             if record.holiday_type == 'employee' and record.type == 'remove':
371                 meeting_obj = self.pool.get('calendar.event')
372                 meeting_vals = {
373                     'name': record.name or _('Leave Request'),
374                     'categ_ids': record.holiday_status_id.categ_id and [(6,0,[record.holiday_status_id.categ_id.id])] or [],
375                     'duration': record.number_of_days_temp * 8,
376                     'description': record.notes,
377                     'user_id': record.user_id.id,
378                     'start': record.date_from,
379                     'stop': record.date_to,
380                     'allday': False,
381                     'state': 'open',            # to block that meeting date in the calendar
382                     'class': 'confidential'
383                 }   
384                 #Add the partner_id (if exist) as an attendee             
385                 if record.user_id and record.user_id.partner_id:
386                     meeting_vals['partner_ids'] = [(4,record.user_id.partner_id.id)]
387                     
388                 meeting_id = meeting_obj.create(cr, uid, meeting_vals)
389                 self._create_resource_leave(cr, uid, [record], context=context)
390                 self.write(cr, uid, ids, {'meeting_id': meeting_id})
391             elif record.holiday_type == 'category':
392                 emp_ids = obj_emp.search(cr, uid, [('category_ids', 'child_of', [record.category_id.id])])
393                 leave_ids = []
394                 for emp in obj_emp.browse(cr, uid, emp_ids):
395                     vals = {
396                         'name': record.name,
397                         'type': record.type,
398                         'holiday_type': 'employee',
399                         'holiday_status_id': record.holiday_status_id.id,
400                         'date_from': record.date_from,
401                         'date_to': record.date_to,
402                         'notes': record.notes,
403                         'number_of_days_temp': record.number_of_days_temp,
404                         'parent_id': record.id,
405                         'employee_id': emp.id
406                     }
407                     leave_ids.append(self.create(cr, uid, vals, context=None))
408                 for leave_id in leave_ids:
409                     # TODO is it necessary to interleave the calls?
410                     for sig in ('confirm', 'validate', 'second_validate'):
411                         self.signal_workflow(cr, uid, [leave_id], sig)
412         return True
413
414     def holidays_confirm(self, cr, uid, ids, context=None):
415         for record in self.browse(cr, uid, ids, context=context):
416             if record.employee_id and record.employee_id.parent_id and record.employee_id.parent_id.user_id:
417                 self.message_subscribe_users(cr, uid, [record.id], user_ids=[record.employee_id.parent_id.user_id.id], context=context)
418         return self.write(cr, uid, ids, {'state': 'confirm'})
419
420     def holidays_refuse(self, cr, uid, ids, context=None):
421         obj_emp = self.pool.get('hr.employee')
422         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
423         manager = ids2 and ids2[0] or False
424         for holiday in self.browse(cr, uid, ids, context=context):
425             if holiday.state == 'validate1':
426                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id': manager})
427             else:
428                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id2': manager})
429         self.holidays_cancel(cr, uid, ids, context=context)
430         return True
431
432     def holidays_cancel(self, cr, uid, ids, context=None):
433         meeting_obj = self.pool.get('calendar.event')
434         for record in self.browse(cr, uid, ids):
435             # Delete the meeting
436             if record.meeting_id:
437                 record.meeting_id.unlink()
438
439             # If a category that created several holidays, cancel all related
440             self.signal_workflow(cr, uid, map(attrgetter('id'), record.linked_request_ids or []), 'refuse')
441
442         self._remove_resource_leave(cr, uid, ids, context=context)
443         return True
444
445     def check_holidays(self, cr, uid, ids, context=None):
446         for record in self.browse(cr, uid, ids, context=context):
447             if record.holiday_type != 'employee' or record.type != 'remove' or not record.employee_id or record.holiday_status_id.limit:
448                 continue
449             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]
450             if leave_days['remaining_leaves'] < 0 or leave_days['virtual_remaining_leaves'] < 0:
451                 # Raising a warning gives a more user-friendly feedback than the default constraint error
452                 raise Warning(_('The number of remaining leaves is not sufficient for this leave type.\n'
453                                 'Please verify also the leaves waiting for validation.'))
454         return True
455
456     # -----------------------------
457     # OpenChatter and notifications
458     # -----------------------------
459
460     def _needaction_domain_get(self, cr, uid, context=None):
461         emp_obj = self.pool.get('hr.employee')
462         empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
463         dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
464         # if this user is a hr.manager, he should do second validations
465         if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
466             dom = ['|'] + dom + [('state', '=', 'validate1')]
467         return dom
468
469     def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
470         for obj in self.browse(cr, uid, ids, context=context):
471             self.message_post(cr, uid, [obj.id],
472                 _("Request approved, waiting second validation."), context=context)
473
474 class resource_calendar_leaves(osv.osv):
475     _inherit = "resource.calendar.leaves"
476     _description = "Leave Detail"
477     _columns = {
478         'holiday_id': fields.many2one("hr.holidays", "Leave Request"),
479     }
480
481
482
483 class hr_employee(osv.osv):
484     _inherit="hr.employee"
485
486     def create(self, cr, uid, vals, context=None):
487         # don't pass the value of remaining leave if it's 0 at the creation time, otherwise it will trigger the inverse
488         # function _set_remaining_days and the system may not be configured for. Note that we don't have this problem on
489         # the write because the clients only send the fields that have been modified.
490         if 'remaining_leaves' in vals and not vals['remaining_leaves']:
491             del(vals['remaining_leaves'])
492         return super(hr_employee, self).create(cr, uid, vals, context=context)
493
494     def _set_remaining_days(self, cr, uid, empl_id, name, value, arg, context=None):
495         employee = self.browse(cr, uid, empl_id, context=context)
496         diff = value - employee.remaining_leaves
497         type_obj = self.pool.get('hr.holidays.status')
498         holiday_obj = self.pool.get('hr.holidays')
499         # Find for holidays status
500         status_ids = type_obj.search(cr, uid, [('limit', '=', False)], context=context)
501         if len(status_ids) != 1 :
502             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)))
503         status_id = status_ids and status_ids[0] or False
504         if not status_id:
505             return False
506         if diff > 0:
507             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)
508         elif diff < 0:
509             raise osv.except_osv(_('Warning!'), _('You cannot reduce validated allocation requests'))
510         else:
511             return False
512         for sig in ('confirm', 'validate', 'second_validate'):
513             holiday_obj.signal_workflow(cr, uid, [leave_id], sig)
514         return True
515
516     def _get_remaining_days(self, cr, uid, ids, name, args, context=None):
517         cr.execute("""SELECT
518                 sum(h.number_of_days) as days,
519                 h.employee_id
520             from
521                 hr_holidays h
522                 join hr_holidays_status s on (s.id=h.holiday_status_id)
523             where
524                 h.state='validate' and
525                 s.limit=False and
526                 h.employee_id in %s
527             group by h.employee_id""", (tuple(ids),))
528         res = cr.dictfetchall()
529         remaining = {}
530         for r in res:
531             remaining[r['employee_id']] = r['days']
532         for employee_id in ids:
533             if not remaining.get(employee_id):
534                 remaining[employee_id] = 0.0
535         return remaining
536
537     def _get_leave_status(self, cr, uid, ids, name, args, context=None):
538         holidays_obj = self.pool.get('hr.holidays')
539         holidays_id = holidays_obj.search(cr, uid,
540            [('employee_id', 'in', ids), ('date_from','<=',time.strftime('%Y-%m-%d %H:%M:%S')),
541            ('date_to','>=',time.strftime('%Y-%m-%d 23:59:59')),('type','=','remove'),('state','not in',('cancel','refuse'))],
542            context=context)
543         result = {}
544         for id in ids:
545             result[id] = {
546                 'current_leave_state': False,
547                 'current_leave_id': False,
548                 'leave_date_from':False,
549                 'leave_date_to':False,
550             }
551         for holiday in self.pool.get('hr.holidays').browse(cr, uid, holidays_id, context=context):
552             result[holiday.employee_id.id]['leave_date_from'] = holiday.date_from
553             result[holiday.employee_id.id]['leave_date_to'] = holiday.date_to
554             result[holiday.employee_id.id]['current_leave_state'] = holiday.state
555             result[holiday.employee_id.id]['current_leave_id'] = holiday.holiday_status_id.id
556         return result
557
558     def _leaves_count(self, cr, uid, ids, field_name, arg, context=None):
559         Holidays = self.pool['hr.holidays']
560         return {
561             employee_id: Holidays.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context) 
562             for employee_id in ids
563         }
564
565     _columns = {
566         '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.'),
567         'current_leave_state': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
568             selection=[('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'),
569             ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
570         'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'),
571         'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'),
572         'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'),
573         'leaves_count': fields.function(_leaves_count, type='integer', string='Leaves'),
574
575     }
576
577
578 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: