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