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