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