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