[FIX] hr_holidays: did some changes.
[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 time
26 from itertools import groupby
27 from operator import attrgetter, itemgetter
28
29 import math
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, return_false, context=None):
40         cr.execute("""SELECT id, type, number_of_days, holiday_status_id FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id in %s""",
41             [employee_id, tuple(ids)])
42         result = sorted(cr.dictfetchall(), key=lambda x: x['holiday_status_id'])
43         grouped_lines = dict((k, [v for v in itr]) for k, itr in groupby(result, itemgetter('holiday_status_id')))
44         res = {}
45         for record in self.browse(cr, uid, ids, context=context):
46             res[record.id] = {}
47             max_leaves = leaves_taken = 0
48             if not return_false:
49                 if record.id in grouped_lines:
50                     leaves_taken = -sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'remove'])
51                     max_leaves = sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'add'])
52             res[record.id]['max_leaves'] = max_leaves
53             res[record.id]['leaves_taken'] = leaves_taken
54             res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
55         return res
56
57     def _user_left_days(self, cr, uid, ids, name, args, context=None):
58         return_false = False
59         employee_id = False
60         res = {}
61         if context and context.has_key('employee_id'):
62             if not context['employee_id']:
63                 return_false = True
64             employee_id = context['employee_id']
65         else:
66             employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
67             if employee_ids:
68                 employee_id = employee_ids[0]
69             else:
70                 return_false = True
71         if employee_id:
72             res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
73         else:
74             res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
75         return res
76
77     _columns = {
78         'name': fields.char('Leave Type', size=64, required=True, translate=True),
79         'categ_id': fields.many2one('crm.meeting.type', 'Meeting Type',
80             help='Once a leave is validated, OpenERP will create a corresponding meeting of this type in the calendar.'),
81         '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.'),
82         '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 take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
83         '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."),
84         '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'),
85         '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'),
86         'remaining_leaves': fields.function(_user_left_days, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
87         'double_validation': fields.boolean('Apply Double Validation', help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved."),
88     }
89     _defaults = {
90         'color_name': 'red',
91         'active': True,
92     }
93
94     def name_get(self, cr, uid, ids, context=None):
95         if not ids:
96             return []
97         res = []
98         for record in self.browse(cr, uid, ids, context=context):
99             name = record.name
100             if not record.limit:
101                 name = name + ('  (%d/%d)' % (record.leaves_taken or 0.0, record.max_leaves or 0.0))
102             res.append((record.id, name))
103         return res
104
105
106 class hr_holidays(osv.osv):
107     _name = "hr.holidays"
108     _description = "Leave"
109     _order = "type desc, date_from asc"
110     _inherit = ['mail.thread', 'ir.needaction_mixin']
111     _track = {
112         'state': {
113             'hr_holidays.mt_holidays_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'validate',
114             'hr_holidays.mt_holidays_refused': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'refuse',
115             'hr_holidays.mt_holidays_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirm',
116         },
117     }
118
119     def _employee_get(self, cr, uid, context=None):
120         ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
121         if ids:
122             return ids[0]
123         return False
124
125     def _compute_number_of_days(self, cr, uid, ids, name, args, context=None):
126         result = {}
127         for hol in self.browse(cr, uid, ids, context=context):
128             if hol.type=='remove':
129                 result[hol.id] = -hol.number_of_days_temp
130             else:
131                 result[hol.id] = hol.number_of_days_temp
132         return result
133
134     def _check_date(self, cr, uid, ids):
135         for holiday in self.browse(cr, uid, ids):
136             holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)])
137             if holiday_ids:
138                 return False
139         return True
140
141     _columns = {
142         'name': fields.char('Description', size=64),
143         'state': fields.selection([('draft', 'To Submit'), ('cancel', 'Cancelled'),('confirm', 'To Approve'), ('refuse', 'Refused'), ('validate1', 'Second Approval'), ('validate', 'Approved')],
144             'Status', readonly=True, track_visibility='onchange',
145             help='The status is set to \'To Submit\', when a holiday request is created.\
146             \nThe status is \'To Approve\', when holiday request is confirmed by user.\
147             \nThe status is \'Refused\', when holiday request is refused by manager.\
148             \nThe status is \'Approved\', when holiday request is approved by manager.'),
149         'user_id':fields.related('employee_id', 'user_id', type='many2one', relation='res.users', string='User', store=True),
150         'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, select=True),
151         'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
152         'holiday_status_id': fields.many2one("hr.holidays.status", "Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
153         'employee_id': fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
154         'manager_id': fields.many2one('hr.employee', 'First Approval', invisible=False, readonly=True, help='This area is automatically filled by the user who validate the leave'),
155         'notes': fields.text('Reasons',readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
156         'number_of_days_temp': fields.float('Allocation', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
157         'number_of_days': fields.function(_compute_number_of_days, string='Number of Days', store=True),
158         'meeting_id': fields.many2one('crm.meeting', 'Meeting'),
159         '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),
160         'parent_id': fields.many2one('hr.holidays', 'Parent'),
161         'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
162         'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
163         'category_id': fields.many2one('hr.employee.category', "Employee Tag", help='Category of Employee', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
164         '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),
165         'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
166         'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
167     }
168     _defaults = {
169         'employee_id': _employee_get,
170         'state': 'draft',
171         'type': 'remove',
172         'user_id': lambda obj, cr, uid, context: uid,
173         'holiday_type': 'employee'
174     }
175     _constraints = [
176         (_check_date, 'You can not have 2 leaves that overlaps on same day!', ['date_from','date_to']),
177     ] 
178     
179     _sql_constraints = [
180         ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "The employee or employee category of this request is missing."),
181         ('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."),
182         ('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
183     ]
184
185     def _create_resource_leave(self, cr, uid, leaves, context=None):
186         '''This method will create entry in resource calendar leave object at the time of holidays validated '''
187         obj_res_leave = self.pool.get('resource.calendar.leaves')
188         for leave in leaves:
189             vals = {
190                 'name': leave.name,
191                 'date_from': leave.date_from,
192                 'holiday_id': leave.id,
193                 'date_to': leave.date_to,
194                 'resource_id': leave.employee_id.resource_id.id,
195                 'calendar_id': leave.employee_id.resource_id.calendar_id.id
196             }
197             obj_res_leave.create(cr, uid, vals, context=context)
198         return True
199
200     def _remove_resource_leave(self, cr, uid, ids, context=None):
201         '''This method will create entry in resource calendar leave object at the time of holidays cancel/removed'''
202         obj_res_leave = self.pool.get('resource.calendar.leaves')
203         leave_ids = obj_res_leave.search(cr, uid, [('holiday_id', 'in', ids)], context=context)
204         return obj_res_leave.unlink(cr, uid, leave_ids, context=context)
205
206     def onchange_type(self, cr, uid, ids, holiday_type):
207         result = {'value': {'employee_id': False}}
208         if holiday_type == 'employee':
209             ids_employee = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
210             if ids_employee:
211                 result['value'] = {
212                     'employee_id': ids_employee[0]
213                 }
214         return result
215
216     def onchange_employee(self, cr, uid, ids, employee_id):
217         result = {'value': {'department_id': False}}
218         if employee_id:
219             employee = self.pool.get('hr.employee').browse(cr, uid, employee_id)
220             result['value'] = {'department_id': employee.department_id.id}
221         return result
222
223     # TODO: can be improved using resource calendar method
224     def _get_number_of_days(self, date_from, date_to):
225         """Returns a float equals to the timedelta between two dates given as string."""
226
227         DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
228         from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
229         to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
230         timedelta = to_dt - from_dt
231         diff_day = timedelta.days + float(timedelta.seconds) / 86400
232         return diff_day
233
234     def unlink(self, cr, uid, ids, context=None):
235         for rec in self.browse(cr, uid, ids, context=context):
236             if rec.state not in ['draft', 'cancel', 'confirm']:
237                 raise osv.except_osv(_('Warning!'),_('You cannot delete a leave which is in %s state.')%(rec.state))
238         return super(hr_holidays, self).unlink(cr, uid, ids, context)
239
240     def onchange_date_from(self, cr, uid, ids, date_to, date_from):
241         """
242         If there are no date set for date_to, automatically set one 8 hours later than
243         the date_from.
244         Also update the number_of_days.
245         """
246         # date_to has to be greater than date_from
247         if (date_from and date_to) and (date_from > date_to):
248             raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
249
250         result = {'value': {}}
251
252         # No date_to set so far: automatically compute one 8 hours later
253         if date_from and not date_to:
254             date_to_with_delta = datetime.datetime.strptime(date_from, tools.DEFAULT_SERVER_DATETIME_FORMAT) + datetime.timedelta(hours=8)
255             result['value']['date_to'] = str(date_to_with_delta)
256
257         # Compute and update the number of days
258         if (date_to and date_from) and (date_from <= date_to):
259             diff_day = self._get_number_of_days(date_from, date_to)
260             result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
261         else:
262             result['value']['number_of_days_temp'] = 0
263
264         return result
265
266     def onchange_date_to(self, cr, uid, ids, date_to, date_from):
267         """
268         Update the number_of_days.
269         """
270
271         # date_to has to be greater than date_from
272         if (date_from and date_to) and (date_from > date_to):
273             raise osv.except_osv(_('Warning!'),_('The start date must be anterior to the end date.'))
274
275         result = {'value': {}}
276
277         # Compute and update the number of days
278         if (date_to and date_from) and (date_from <= date_to):
279             diff_day = self._get_number_of_days(date_from, date_to)
280             result['value']['number_of_days_temp'] = round(math.floor(diff_day))+1
281         else:
282             result['value']['number_of_days_temp'] = 0
283
284         return result
285
286     def create(self, cr, uid, values, context=None):
287         """ Override to avoid automatic logging of creation """
288         if context is None:
289             context = {}
290         context = dict(context, mail_create_nolog=True)
291         return super(hr_holidays, self).create(cr, uid, values, context=context)
292
293     def write(self, cr, uid, ids, vals, context=None):
294         check_fnct = self.pool.get('hr.holidays.status').check_access_rights
295         for  holiday in self.browse(cr, uid, ids, context=context):
296             if holiday.state in ('validate','validate1') and not check_fnct(cr, uid, 'write', raise_exception=False):
297                 raise osv.except_osv(_('Warning!'),_('You cannot modify a leave request that has been approved. Contact a human resource manager.'))
298         return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
299
300     def set_to_draft(self, cr, uid, ids, context=None):
301         self.write(cr, uid, ids, {
302             'state': 'draft',
303             'manager_id': False,
304             'manager_id2': False,
305         })
306         self.delete_workflow(cr, uid, ids)
307         self.create_workflow(cr, uid, ids)
308         to_unlink = []
309         for record in self.browse(cr, uid, ids, context=context):
310             for record2 in record.linked_request_ids:
311                 self.set_to_draft(cr, uid, [record2.id], context=context)
312                 to_unlink.append(record2.id)
313         if to_unlink:
314             self.unlink(cr, uid, to_unlink, context=context)
315         return True
316
317     def holidays_first_validate(self, cr, uid, ids, context=None):
318         self.check_holidays(cr, uid, ids, context=context)
319         obj_emp = self.pool.get('hr.employee')
320         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
321         manager = ids2 and ids2[0] or False
322         self.holidays_first_validate_notificate(cr, uid, ids, context=context)
323         return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
324
325     def holidays_validate(self, cr, uid, ids, context=None):
326         self.check_holidays(cr, uid, ids, context=context)
327         obj_emp = self.pool.get('hr.employee')
328         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
329         manager = ids2 and ids2[0] or False
330         self.write(cr, uid, ids, {'state':'validate'})
331         data_holiday = self.browse(cr, uid, ids)
332         for record in data_holiday:
333             if record.double_validation:
334                 self.write(cr, uid, [record.id], {'manager_id2': manager})
335             else:
336                 self.write(cr, uid, [record.id], {'manager_id': manager})
337             if record.holiday_type == 'employee' and record.type == 'remove':
338                 meeting_obj = self.pool.get('crm.meeting')
339                 meeting_vals = {
340                     'name': record.name or _('Leave Request'),
341                     'categ_ids': record.holiday_status_id.categ_id and [(6,0,[record.holiday_status_id.categ_id.id])] or [],
342                     'duration': record.number_of_days_temp * 8,
343                     'description': record.notes,
344                     'user_id': record.user_id.id,
345                     'date': record.date_from,
346                     'end_date': record.date_to,
347                     'date_deadline': record.date_to,
348                     'state': 'open',            # to block that meeting date in the calendar
349                 }
350                 meeting_id = meeting_obj.create(cr, uid, meeting_vals)
351                 self._create_resource_leave(cr, uid, [record], context=context)
352                 self.write(cr, uid, ids, {'meeting_id': meeting_id})
353             elif record.holiday_type == 'category':
354                 emp_ids = obj_emp.search(cr, uid, [('category_ids', 'child_of', [record.category_id.id])])
355                 leave_ids = []
356                 for emp in obj_emp.browse(cr, uid, emp_ids):
357                     vals = {
358                         'name': record.name,
359                         'type': record.type,
360                         'holiday_type': 'employee',
361                         'holiday_status_id': record.holiday_status_id.id,
362                         'date_from': record.date_from,
363                         'date_to': record.date_to,
364                         'notes': record.notes,
365                         'number_of_days_temp': record.number_of_days_temp,
366                         'parent_id': record.id,
367                         'employee_id': emp.id
368                     }
369                     leave_ids.append(self.create(cr, uid, vals, context=None))
370                 for leave_id in leave_ids:
371                     # TODO is it necessary to interleave the calls?
372                     self.signal_confirm(cr, uid, [leave_id])
373                     self.signal_validate(cr, uid, [leave_id])
374                     self.signal_second_validate(cr, uid, [leave_id])
375         return True
376
377     def holidays_confirm(self, cr, uid, ids, context=None):
378         self.check_holidays(cr, uid, ids, context=context)
379         for record in self.browse(cr, uid, ids, context=context):
380             if record.employee_id and record.employee_id.parent_id and record.employee_id.parent_id.user_id:
381                 self.message_subscribe_users(cr, uid, [record.id], user_ids=[record.employee_id.parent_id.user_id.id], context=context)
382         return self.write(cr, uid, ids, {'state': 'confirm'})
383
384     def holidays_refuse(self, cr, uid, ids, context=None):
385         obj_emp = self.pool.get('hr.employee')
386         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
387         manager = ids2 and ids2[0] or False
388         for holiday in self.browse(cr, uid, ids, context=context):
389             if holiday.state == 'validate1':
390                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id': manager})
391             else:
392                 self.write(cr, uid, [holiday.id], {'state': 'refuse', 'manager_id2': manager})
393         self.holidays_cancel(cr, uid, ids, context=context)
394         return True
395
396     def holidays_cancel(self, cr, uid, ids, context=None):
397         meeting_obj = self.pool.get('crm.meeting')
398         for record in self.browse(cr, uid, ids):
399             # Delete the meeting
400             if record.meeting_id:
401                 meeting_obj.unlink(cr, uid, [record.meeting_id.id])
402
403             # If a category that created several holidays, cancel all related
404             self.signal_refuse(cr, uid, map(attrgetter('id'), record.linked_request_ids or []))
405
406         self._remove_resource_leave(cr, uid, ids, context=context)
407         return True
408
409     def check_holidays(self, cr, uid, ids, context=None):
410         holi_status_obj = self.pool.get('hr.holidays.status')
411         for record in self.browse(cr, uid, ids):
412             if record.holiday_type == 'employee' and record.type == 'remove':
413                 if record.employee_id and not record.holiday_status_id.limit:
414                     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']
415                     if leaves_rest < record.number_of_days_temp:
416                         raise osv.except_osv(_('Warning!'), _('There are not enough %s allocated for employee %s; please create an allocation request for this leave type.') % (record.holiday_status_id.name, record.employee_id.name))
417         return True
418
419     # -----------------------------
420     # OpenChatter and notifications
421     # -----------------------------
422
423     def _needaction_domain_get(self, cr, uid, context=None):
424         emp_obj = self.pool.get('hr.employee')
425         empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
426         dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
427         # if this user is a hr.manager, he should do second validations
428         if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
429             dom = ['|'] + dom + [('state', '=', 'validate1')]
430         return dom
431
432     def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
433         for obj in self.browse(cr, uid, ids, context=context):
434             self.message_post(cr, uid, [obj.id],
435                 _("Request approved, waiting second validation."), context=context)
436
437 class resource_calendar_leaves(osv.osv):
438     _inherit = "resource.calendar.leaves"
439     _description = "Leave Detail"
440     _columns = {
441         'holiday_id': fields.many2one("hr.holidays", "Leave Request"),
442     }
443
444 resource_calendar_leaves()
445
446
447 class hr_employee(osv.osv):
448     _inherit="hr.employee"
449
450     def create(self, cr, uid, vals, context=None):
451         # don't pass the value of remaining leave if it's 0 at the creation time, otherwise it will trigger the inverse
452         # function _set_remaining_days and the system may not be configured for. Note that we don't have this problem on
453         # the write because the clients only send the fields that have been modified.
454         if 'remaining_leaves' in vals and not vals['remaining_leaves']:
455             del(vals['remaining_leaves'])
456         return super(hr_employee, self).create(cr, uid, vals, context=context)
457
458     def _set_remaining_days(self, cr, uid, empl_id, name, value, arg, context=None):
459         employee = self.browse(cr, uid, empl_id, context=context)
460         diff = value - employee.remaining_leaves
461         type_obj = self.pool.get('hr.holidays.status')
462         holiday_obj = self.pool.get('hr.holidays')
463         # Find for holidays status
464         status_ids = type_obj.search(cr, uid, [('limit', '=', False)], context=context)
465         if len(status_ids) != 1 :
466             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)))
467         status_id = status_ids and status_ids[0] or False
468         if not status_id:
469             return False
470         if diff > 0:
471             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)
472         elif diff < 0:
473             leave_id = holiday_obj.create(cr, uid, {'name': _('Leave Request for %s') % employee.name, 'employee_id': employee.id, 'holiday_status_id': status_id, 'type': 'remove', 'holiday_type': 'employee', 'number_of_days_temp': abs(diff)}, context=context)
474         else:
475             return False
476         holidays_obj.signal_confirm(cr, uid, [leave_id])
477         holidays_obj.signal_validate(cr, uid, [leave_id])
478         holidays_obj.signal_second_validate(cr, uid, [leave_id])
479         return True
480
481     def _get_remaining_days(self, cr, uid, ids, name, args, context=None):
482         cr.execute("""SELECT
483                 sum(h.number_of_days) as days,
484                 h.employee_id
485             from
486                 hr_holidays h
487                 join hr_holidays_status s on (s.id=h.holiday_status_id)
488             where
489                 h.state='validate' and
490                 s.limit=False and
491                 h.employee_id in (%s)
492             group by h.employee_id"""% (','.join(map(str,ids)),) )
493         res = cr.dictfetchall()
494         remaining = {}
495         for r in res:
496             remaining[r['employee_id']] = r['days']
497         for employee_id in ids:
498             if not remaining.get(employee_id):
499                 remaining[employee_id] = 0.0
500         return remaining
501
502     def _get_leave_status(self, cr, uid, ids, name, args, context=None):
503         holidays_obj = self.pool.get('hr.holidays')
504         holidays_id = holidays_obj.search(cr, uid,
505            [('employee_id', 'in', ids), ('date_from','<=',time.strftime('%Y-%m-%d %H:%M:%S')),
506            ('date_to','>=',time.strftime('%Y-%m-%d 23:59:59')),('type','=','remove'),('state','not in',('cancel','refuse'))],
507            context=context)
508         result = {}
509         for id in ids:
510             result[id] = {
511                 'current_leave_state': False,
512                 'current_leave_id': False,
513                 'leave_date_from':False,
514                 'leave_date_to':False,
515             }
516         for holiday in self.pool.get('hr.holidays').browse(cr, uid, holidays_id, context=context):
517             result[holiday.employee_id.id]['leave_date_from'] = holiday.date_from
518             result[holiday.employee_id.id]['leave_date_to'] = holiday.date_to
519             result[holiday.employee_id.id]['current_leave_state'] = holiday.state
520             result[holiday.employee_id.id]['current_leave_id'] = holiday.holiday_status_id.id
521         return result
522
523     _columns = {
524         '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.'),
525         'current_leave_state': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Status", type="selection",
526             selection=[('draft', 'New'), ('confirm', 'Waiting Approval'), ('refuse', 'Refused'),
527             ('validate1', 'Waiting Second Approval'), ('validate', 'Approved'), ('cancel', 'Cancelled')]),
528         'current_leave_id': fields.function(_get_leave_status, multi="leave_status", string="Current Leave Type",type='many2one', relation='hr.holidays.status'),
529         'leave_date_from': fields.function(_get_leave_status, multi='leave_status', type='date', string='From Date'),
530         'leave_date_to': fields.function(_get_leave_status, multi='leave_status', type='date', string='To Date'),
531     }
532
533 hr_employee()
534
535 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: