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