[FIX][ADD][IMP] hr_holidays: fixed the used 'category_ids'(M2M) field of employee...
[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 time
25 import datetime
26
27 import netsvc
28 from osv import fields, osv
29 from tools.translate import _
30
31 class hr_holidays_status(osv.osv):
32     _name = "hr.holidays.status"
33     _description = "Leave Type"
34
35     def get_days_cat(self, cr, uid, ids, category_id, return_false, context=None):
36         if context is None:
37             context = {}
38         res = {}
39         for record in self.browse(cr, uid, ids, context=context):
40             res[record.id] = {}
41             max_leaves = leaves_taken = 0
42             if not return_false:
43                 cr.execute("""SELECT type, sum(number_of_days) FROM hr_holidays WHERE category_id = %s AND state='validate' AND holiday_status_id = %s GROUP BY type""", (str(category_id), str(record.id)))
44                 for line in cr.fetchall():
45                     if line[0] =='remove':
46                         leaves_taken = -line[1]
47                     if line[0] =='add':
48                         max_leaves = line[1]
49             res[record.id]['max_leaves'] = max_leaves
50             res[record.id]['leaves_taken'] = leaves_taken
51             res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
52         return res
53
54     def get_days(self, cr, uid, ids, employee_id, return_false, context=None):
55         if context is None:
56             context = {}
57         res = {}
58         for record in self.browse(cr, uid, ids, context=context):
59             res[record.id] = {}
60             max_leaves = leaves_taken = 0
61             if not return_false:
62                 cr.execute("""SELECT type, sum(number_of_days) FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id = %s GROUP BY type""", (str(employee_id), str(record.id)))
63                 for line in cr.fetchall():
64                     if line[0] =='remove':
65                         leaves_taken = -line[1]
66                     if line[0] =='add':
67                         max_leaves = line[1]
68             res[record.id]['max_leaves'] = max_leaves
69             res[record.id]['leaves_taken'] = leaves_taken
70             res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
71         return res
72
73     def _user_left_days(self, cr, uid, ids, name, args, context=None):
74         if context is None:
75             context = {}
76         return_false = False
77         employee_id = False
78         res = {}
79
80         if context and context.has_key('employee_id'):
81             if not context['employee_id']:
82                 return_false = True
83             employee_id = context['employee_id']
84         else:
85             employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
86             if employee_ids:
87                 employee_id = employee_ids[0]
88             else:
89                 return_false = True
90         if employee_id:
91             res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
92         else:
93             res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
94         return res
95
96     # To do: we can add remaining_leaves_category field to display remaining leaves for particular type
97     _columns = {
98         'name': fields.char('Name', size=64, required=True, translate=True),
99         'categ_id': fields.many2one('crm.case.categ', 'Meeting Category', domain="[('object_id.model', '=', 'crm.meeting')]", help='If you link this type of leave with a category in the CRM, it will synchronize each leave asked with a case in this category, to display it in the company shared calendar for example.'),
100         'color_name': fields.selection([('red', 'Red'), ('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 Departement'),
101         'limit': fields.boolean('Allow to Override Limit', help='If you tick this checkbox, the system will allow, for this section, the employees to take more leaves than the available ones.'),
102         '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."),
103         'max_leaves': fields.function(_user_left_days, method=True, string='Maximum Leaves Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
104         'leaves_taken': fields.function(_user_left_days, method=True, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
105         'remaining_leaves': fields.function(_user_left_days, method=True, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
106         'double_validation': fields.boolean('Apply Double Validation', help="If its True then its Allocation/Request have to be validated by second validator")
107     }
108     _defaults = {
109         'color_name': 'red',
110         'active': True,
111         }
112
113 hr_holidays_status()
114
115 class hr_holidays(osv.osv):
116     _name = "hr.holidays"
117     _description = "Leave"
118     _order = "type desc, date_from asc"
119
120     def _employee_get(obj, cr, uid, context=None):
121         if context is None:
122             context = {}
123         ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
124         if ids:
125             return ids[0]
126         return False
127
128     _columns = {
129         'name': fields.char('Description', required=True, readonly=True, size=64, states={'draft':[('readonly',False)]}),
130         'state': fields.selection([('draft', 'Draft'), ('confirm', 'Waiting Validation'), ('refuse', 'Refused'), ('validate1', 'Waiting Second Validation'), ('validate', 'Validated'), ('cancel', 'Cancelled')], 'State', readonly=True, help='When the holiday request is created the state is \'Draft\'.\n It is confirmed by the user and request is sent to admin, the state is \'Waiting Validation\'.\
131             If the admin accepts it, the state is \'Validated\'. If it is refused, the state is \'Refused\'.'),
132         'date_from': fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)]}),
133         'user_id':fields.many2one('res.users', 'User', states={'draft':[('readonly',False)]}, select=True, readonly=True),
134         'date_to': fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)]}),
135         'holiday_status_id' : fields.many2one("hr.holidays.status", " Leave  Type", required=True,readonly=True, states={'draft':[('readonly',False)]}),
136         'employee_id' : fields.many2one('hr.employee', "Employee", select=True, invisible=False, readonly=True, states={'draft':[('readonly',False)]}, help='Leave Manager can let this field empty if this leave request/allocation is for every employee'),
137         'manager_id': fields.many2one('hr.employee', 'Leave Manager', invisible=False, readonly=True, help='This area is automaticly filled by the user who validate the leave'),
138         'notes': fields.text('Notes',readonly=True, states={'draft':[('readonly',False)]}),
139         'number_of_days': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
140         'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
141         'case_id': fields.many2one('crm.meeting', 'Case'),
142         'type': fields.selection([('remove','Leave Request'),('add','Allocation Request')], 'Request Type', required=True, readonly=True, states={'draft':[('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"),
143         'allocation_type': fields.selection([('employee','Employee Request'),('company','Company Allocation')], 'Allocation Type', required=True, readonly=True, states={'draft':[('readonly',False)]}, help='This field is only for informative purposes, to depict if the leave request/allocation comes from an employee or from the company'),
144         'parent_id': fields.many2one('hr.holidays', 'Parent'),
145         'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
146         'department_id':fields.related('employee_id', 'department_id', string='Department', type='many2one', relation='hr.department', readonly=True, store=True),
147         'category_id': fields.many2one('hr.employee.category', "Category", help='Category Of employee'),
148         'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Category')], 'Holiday Type', help='By Employee: Allocation/Request for individual Employee, By Employee Category: Allocation/Request for group of employees in category'),
149         'manager_id2': fields.many2one('hr.employee', 'Second Validator', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)')
150             }
151
152     _defaults = {
153         'employee_id': _employee_get ,
154         'state': 'draft',
155         'type': 'remove',
156         'allocation_type': 'employee',
157         'user_id': lambda obj, cr, uid, context: uid,
158         'holiday_type': 'employee'
159         }
160
161     def _create_resource_leave(self, cr, uid, vals, context=None):
162         '''This method will create entry in resource calendar leave object at the time of holidays validated '''
163         if context is None:
164             context = {}
165         obj_res_leave = self.pool.get('resource.calendar.leaves')
166         return obj_res_leave.create(cr, uid, vals, context=context)
167
168     def _remove_resouce_leave(self, cr, uid, ids, context=None):
169         '''This method will create entry in resource calendar leave object at the time of holidays cancel/removed'''
170         obj_res_leave = self.pool.get('resource.calendar.leaves')
171         if context is None:
172             context = {}
173         leave_ids = obj_res_leave.search(cr, uid, [('holiday_id', 'in', ids)], context=context)
174         return obj_res_leave.unlink(cr, uid, leave_ids)
175
176     def create(self, cr, uid, vals, context=None):
177         if context is None:
178             context = {}
179         if 'holiday_type' in vals:
180             if vals['holiday_type'] == 'employee':
181                 vals.update({'category_id': False})
182             else:
183                 vals.update({'employee_id': False})
184         if context.has_key('type'):
185             vals['type'] = context['type']
186         if context.has_key('allocation_type'):
187             vals['allocation_type'] = context['allocation_type']
188         return super(hr_holidays, self).create(cr, uid, vals, context=context)
189
190     def write(self, cr, uid, ids, vals, context=None):
191         if context is None:
192             context = {}
193         if 'holiday_type' in vals:
194             if vals['holiday_type'] == 'employee':
195                 vals.update({'category_id': False})
196             else:
197                 vals.update({'employee_id': False})
198         return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
199
200     def onchange_type(self, cr, uid, ids, holiday_type):
201         result = {}
202         if holiday_type=='employee':
203             ids_employee = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
204             if ids_employee:
205                 result['value'] = {
206                     'employee_id': ids_employee[0]
207                                     }
208         return result
209
210     def _get_number_of_days(self, date_from, date_to):
211         """Returns a float equals to the timedelta between two dates given as string."""
212
213         DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
214         from_dt = datetime.datetime.strptime(date_from, DATETIME_FORMAT)
215         to_dt = datetime.datetime.strptime(date_to, DATETIME_FORMAT)
216         timedelta = to_dt - from_dt
217         diff_day = timedelta.days + float(timedelta.seconds) / 86400
218         return diff_day
219
220     def _update_user_holidays(self, cr, uid, ids):
221         for record in self.browse(cr, uid, ids):
222             if record.state=='validate':
223                 if record.case_id:
224                     self.pool.get('crm.meeting').unlink(cr, uid, [record.case_id.id])
225                 if record.linked_request_ids:
226                     list_ids = []
227                     [list_ids.append(i) for id in record.linked_request_ids]
228                     self.holidays_cancel(cr, uid, list_ids)
229                     self.unlink(cr, uid, list_ids)
230
231     def _check_date(self, cr, uid, ids):
232         for rec in self.read(cr, uid, ids, ['number_of_days_temp', 'date_from','date_to', 'type']):
233             if rec['number_of_days_temp'] < 0:
234                 return False
235             if rec['type'] == 'add':
236                 continue
237             date_from = time.strptime(rec['date_from'], '%Y-%m-%d %H:%M:%S')
238             date_to = time.strptime(rec['date_to'], '%Y-%m-%d %H:%M:%S')
239             if date_from > date_to:
240                 return False
241         return True
242
243     _constraints = [(_check_date, 'Start date should not be larger than end date!\nNumber of Days should be greater than 1!', ['number_of_days_temp'])]
244
245     def unlink(self, cr, uid, ids, context=None):
246         if context is None:
247             context = {}
248         self._update_user_holidays(cr, uid, ids)
249         self._remove_resouce_leave(cr, uid, ids, context=context)
250         return super(hr_holidays, self).unlink(cr, uid, ids, context)
251
252     def onchange_date_from(self, cr, uid, ids, date_to, date_from):
253         result = {}
254         if date_to and date_from:
255             diff_day = self._get_number_of_days(date_from, date_to)
256             result['value'] = {
257                 'number_of_days_temp': round(diff_day)+1
258             }
259             return result
260         result['value'] = {
261             'number_of_days_temp': 0,
262         }
263         return result
264
265     def onchange_date_to(self, cr, uid, ids, date_from, date_to):
266         return self.onchange_date_from(cr, uid, ids, date_to, date_from)
267
268     def onchange_sec_id(self, cr, uid, ids, status, context=None):
269         if context is None:
270             context = {}
271         warning = {}
272         if status:
273             brows_obj = self.pool.get('hr.holidays.status').browse(cr, uid, [status], context=context)[0]
274             if brows_obj.categ_id and brows_obj.categ_id.section_id and not brows_obj.categ_id.section_id.allow_unlink:
275                 warning = {
276                     'title': "Warning for ",
277                     'message': "You won\'t be able to cancel this leave request because the CRM Sales Team of the leave type disallows."
278                         }
279         return {'warning': warning}
280
281     def set_to_draft(self, cr, uid, ids, *args):
282         wf_service = netsvc.LocalService("workflow")
283         self.write(cr, uid, ids, {
284             'state': 'draft',
285             'manager_id': False,
286             'number_of_days': 0,
287         })
288         for id in ids:
289             wf_service.trg_create(uid, 'hr.holidays', id, cr)
290         return True
291
292     def holidays_validate2(self, cr, uid, ids, *args):
293         vals = {'state':'validate1'}
294         self.check_holidays(cr, uid, ids)
295         ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)])
296         if ids2:
297             vals['manager_id'] = ids2[0]
298         else:
299             raise osv.except_osv(_('Warning !'),_('No user related to the selected employee.'))
300         self.write(cr, uid, ids, vals)
301         return True
302
303     def holidays_validate(self, cr, uid, ids, *args):
304         obj_emp = self.pool.get('hr.employee')
305         data_holiday = self.browse(cr, uid, ids)
306         self.check_holidays(cr, uid, ids)
307         vals = {'state':'validate'}
308         ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
309         if ids2:
310             if data_holiday[0].state == 'validate1':
311                 vals['manager_id2'] = ids2[0]
312             else:
313                 vals['manager_id'] = ids2[0]
314         else:
315             raise osv.except_osv(_('Warning !'), _('No user related to the selected employee.'))
316         self.write(cr, uid, ids, vals)
317         for record in data_holiday:
318             if record.holiday_type == 'employee' and record.type == 'remove':
319                 vals = {
320                    'name': record.name,
321                    'date_from': record.date_from,
322                    'date_to': record.date_to,
323                    'calendar_id': record.employee_id.calendar_id.id,
324                    'company_id': record.employee_id.company_id.id,
325                    'resource_id': record.employee_id.resource_id.id,
326                    'holiday_id': record.id
327                      }
328                 self._create_resource_leave(cr, uid, vals)
329             elif record.holiday_type == 'category' and record.type == 'remove':
330                 emp_ids = obj_emp.search(cr, uid, [('category_ids', '=', record.category_id.id)])
331                 for emp in obj_emp.browse(cr, uid, emp_ids):
332                     vals = {
333                        'name': record.name,
334                        'date_from': record.date_from,
335                        'date_to': record.date_to,
336                        'calendar_id': emp.calendar_id.id,
337                        'company_id': emp.company_id.id,
338                        'resource_id': emp.resource_id.id,
339                        'holiday_id':record.id
340                          }
341                     self._create_resource_leave(cr, uid, vals)
342         return True
343
344     def holidays_confirm(self, cr, uid, ids, *args):
345         for record in self.browse(cr, uid, ids):
346             user_id = False
347             leave_asked = record.number_of_days_temp
348             if record.holiday_type == 'employee' and record.type == 'remove':
349                 if record.employee_id and not record.holiday_status_id.limit:
350                     leaves_rest = self.pool.get('hr.holidays.status').get_days( cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
351                     if leaves_rest < leave_asked:
352                         raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.employee_id.name)))
353                 nb = -(record.number_of_days_temp)
354             elif record.holiday_type == 'category' and record.type == 'remove':
355                 if record.category_id and not record.holiday_status_id.limit:
356                     leaves_rest = self.pool.get('hr.holidays.status').get_days_cat( cr, uid, [record.holiday_status_id.id], record.category_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
357                     if leaves_rest < leave_asked:
358                         raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.category_id.name)))
359                 nb = -(record.number_of_days_temp)
360             else:
361                 nb = record.number_of_days_temp
362
363             if record.holiday_type == 'employee' and record.employee_id:
364                 user_id = record.employee_id.user_id and record.employee_id.user_id.id or uid
365
366             self.write(cr, uid, [record.id], {'state':'confirm', 'number_of_days': nb, 'user_id': user_id })
367         return True
368
369     def holidays_refuse(self, cr, uid, ids, *args):
370         vals = {'state': 'refuse'}
371         ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
372         if ids2:
373             vals['manager_id'] = ids2[0]
374         self.write(cr, uid, ids, vals)
375         return True
376
377     def holidays_cancel(self, cr, uid, ids, *args):
378         self._update_user_holidays(cr, uid, ids)
379         self.write(cr, uid, ids, {'state': 'cancel'})
380         self._remove_resouce_leave(cr, uid, ids)
381         return True
382
383     def holidays_draft(self, cr, uid, ids, *args):
384         self.write(cr, uid, ids, {'state': 'draft'})
385         return True
386
387     def check_holidays(self, cr, uid, ids):
388         holi_status_obj = self.pool.get('hr.holidays.status')
389         emp_obj = self.pool.get('hr.employee')
390         meeting_obj = self.pool.get('crm.meeting')
391         for record in self.browse(cr, uid, ids):
392             if not record.number_of_days:
393                 raise osv.except_osv(_('Warning!'), _('Wrong leave definition.'))
394             if record.holiday_type=='employee' and record.employee_id:
395                 leave_asked = record.number_of_days
396                 if leave_asked < 0.00:
397                     if not record.holiday_status_id.limit:
398                         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']
399                         if leaves_rest < -(leave_asked):
400                             raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
401             elif record.holiday_type == 'category' and record.category_id:
402                 leave_asked = record.number_of_days
403                 if leave_asked < 0.00:
404                     if not record.holiday_status_id.limit:
405                         leaves_rest = holi_status_obj.get_days_cat(cr, uid, [record.holiday_status_id.id], record.category_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
406                         if leaves_rest < -(leave_asked):
407                             raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
408             else:# This condition will never meet!!
409                 holiday_ids = []
410                 vals = {
411                     'name' : record.name,
412                     'holiday_status_id' : record.holiday_status_id.id,
413                     'state': 'draft',
414                     'date_from' : record.date_from,
415                     'date_to' : record.date_to,
416                     'notes' : record.notes,
417                     'number_of_days': record.number_of_days,
418                     'number_of_days_temp': record.number_of_days_temp,
419                     'type': record.type,
420                     'allocation_type': record.allocation_type,
421                     'parent_id': record.id,
422                 }
423                 employee_ids = emp_obj.search(cr, uid, [])
424                 for employee in employee_ids:
425                     vals['employee_id'] = employee
426                     user_id = emp_obj.search(cr, uid, [('user_id','=',uid)])
427                     if user_id:
428                         vals['user_id'] = user_id[0]
429                     holiday_ids.append(self.create(cr, uid, vals, context=None))
430                 self.holidays_confirm(cr, uid, holiday_ids)
431                 self.holidays_validate(cr, uid, holiday_ids)
432
433             #if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
434 #            if record.holiday_status_id.categ_id and record.date_from and record.date_to:
435 #                vals={}
436 #                vals['name']=record.name
437 #                vals['categ_id']=record.holiday_status_id.categ_id.id
438 #                epoch_c = time.mktime(time.strptime(record.date_to,'%Y-%m-%d %H:%M:%S'))
439 #                epoch_d = time.mktime(time.strptime(record.date_from,'%Y-%m-%d %H:%M:%S'))
440 #                diff_day = (epoch_c - epoch_d)/(3600*24)
441 #                vals['duration'] = (diff_day) * 8
442 #                vals['note'] = record.notes
443 ##                vals['user_id'] = record.user_id.id
444 #                vals['date'] = record.date_from
445 #                if record.holiday_type=='employee':
446 #                    vals['user_id'] = record.user_id.id
447             if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
448                 diff_day = self._get_number_of_days(record.date_from, record.date_to)
449                 vals = {
450                     'name' : record.name,
451                     'categ_id' : record.holiday_status_id.categ_id.id,
452                     'duration' : (diff_day) * 8,
453                     'note' : record.notes,
454                     'user_id' : record.user_id.id,
455                     'date' : record.date_from,
456                 }
457                 case_id = meeting_obj.create(cr, uid, vals)
458                 self.write(cr, uid, ids, {'case_id': case_id})
459
460         return True
461
462 hr_holidays()
463
464 class resource_calendar_leaves(osv.osv):
465     _inherit = "resource.calendar.leaves"
466     _description = "Leave Detail"
467     _columns = {
468         'holiday_id': fields.many2one("hr.holidays", "Holiday"),
469         }
470
471 resource_calendar_leaves()
472
473 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: