1 # -*- coding: utf-8 -*-
2 ##################################################################################
4 # Copyright (c) 2005-2006 Axelor SARL. (http://www.axelor.com)
5 # and 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # $Id: hr.py 4656 2006-11-24 09:58:42Z Cyp $
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.
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.
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/>.
22 ##############################################################################
24 from mx import DateTime
29 from osv import fields, osv
30 from tools.translate import _
32 class hr_holidays_status(osv.osv):
33 _name = "hr.holidays.status"
34 _description = "Leave Types"
35 def get_days(self, cr, uid, ids, employee_id, return_false, context={}):
37 for record in self.browse(cr, uid, ids, context):
39 max_leaves = leaves_taken = 0
41 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)))
42 for line in cr.fetchall():
43 if line[0] =='remove':
44 leaves_taken = -line[1]
47 res[record.id]['max_leaves'] = max_leaves
48 res[record.id]['leaves_taken'] = leaves_taken
49 res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
52 def _user_left_days(self, cr, uid, ids, name, args, context={}):
55 if context and context.has_key('employee_id'):
56 if not context['employee_id']:
58 employee_id = context['employee_id']
60 employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)])
62 employee_id = employee_ids[0]
66 res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
70 'name' : fields.char('Name', size=64, required=True, translate=True),
71 '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.'),
72 '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'),
73 '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.'),
74 '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."),
75 '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'),
76 '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'),
77 'remaining_leaves' : fields.function(_user_left_days, method=True, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
81 'color_name': lambda *args: 'red',
82 'active' : lambda *a: True,
86 class hr_holidays(osv.osv):
88 _description = "Holidays"
89 _order = "type desc, date_from asc"
91 def _employee_get(obj,cr,uid,context={}):
92 ids = obj.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
98 'name' : fields.char('Description', required=True, readonly=True, size=64, states={'draft':[('readonly',False)]}),
99 'state': fields.selection([('draft', 'Draft'), ('confirm', 'Waiting Validation'), ('refuse', 'Refused'), ('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\'.\
100 If the admin accepts it, the state is \'Validated\'. If it is refused, the state is \'Refused\'.'),
101 'date_from' : fields.datetime('Start Date', readonly=True, states={'draft':[('readonly',False)]}),
102 'user_id':fields.many2one('res.users', 'User', states={'draft':[('readonly',False)]}, select=True, readonly=True),
103 'date_to' : fields.datetime('End Date', readonly=True, states={'draft':[('readonly',False)]}),
104 'holiday_status_id' : fields.many2one("hr.holidays.status", "Leave Type", required=True,readonly=True, states={'draft':[('readonly',False)]}),
105 'employee_id' : fields.many2one('hr.employee', "Employee's Name", 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'),
106 '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'),
107 'notes' : fields.text('Notes',readonly=True, states={'draft':[('readonly',False)]}),
108 'number_of_days': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
109 'number_of_days_temp': fields.float('Number of Days', readonly=True, states={'draft':[('readonly',False)]}),
110 'case_id': fields.many2one('crm.meeting', 'Case'),
111 '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"),
112 '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'),
113 'parent_id': fields.many2one('hr.holidays', 'Parent'),
114 'linked_request_ids': fields.one2many('hr.holidays', 'parent_id', 'Linked Requests',),
115 'department_id':fields.many2one('hr.department','Department', readonly=True, states={'draft':[('readonly',False)]} ),
119 'employee_id' : _employee_get ,
120 'state' : lambda *a: 'draft',
121 'type': lambda *a: 'remove',
122 'allocation_type': lambda *a: 'employee',
123 'user_id': lambda obj, cr, uid, context: uid,
125 _order = 'date_from desc'
127 def create(self, cr, uid, vals, context={}):
129 if context.has_key('type'):
130 vals['type'] = context['type']
131 if context.has_key('allocation_type'):
132 vals['allocation_type'] = context['allocation_type']
133 return super(osv.osv,self).create(cr, uid, vals, context)
135 def onchange_date_from(self, cr, uid, ids, date_to, date_from):
137 if date_to and date_from:
138 from_dt = time.mktime(time.strptime(date_from,'%Y-%m-%d %H:%M:%S'))
139 to_dt = time.mktime(time.strptime(date_to,'%Y-%m-%d %H:%M:%S'))
140 diff_day = (to_dt-from_dt)/(3600*24)
142 'number_of_days_temp': round(diff_day)+1
146 'number_of_days_temp': 0,
150 def _update_user_holidays(self, cr, uid, ids):
151 for record in self.browse(cr, uid, ids):
152 if record.state=='validate':
154 self.pool.get('crm.meeting').unlink(cr,uid,[record.case_id.id])
155 if record.linked_request_ids:
157 for id in record.linked_request_ids:
158 list_ids.append(id.id)
159 self.holidays_cancel(cr,uid,list_ids)
160 self.unlink(cr,uid,list_ids)
162 def _check_date(self, cr, uid, ids):
164 cr.execute('select number_of_days_temp from hr_holidays where id in ('+','.join(map(str, ids))+')')
166 if res and res[0][0] < 0:
170 _constraints = [(_check_date, 'Start date should not be larger than end date! ', ['number_of_days'])]
173 def unlink(self, cr, uid, ids, context={}):
174 self._update_user_holidays(cr, uid, ids)
175 return super(hr_holidays, self).unlink(cr, uid, ids, context)
178 def onchange_date_to(self, cr, uid, ids, date_from, date_to):
180 if date_from and date_to:
181 from_dt = time.mktime(time.strptime(date_from,'%Y-%m-%d %H:%M:%S'))
182 to_dt = time.mktime(time.strptime(date_to,'%Y-%m-%d %H:%M:%S'))
183 diff_day = (to_dt-from_dt)/(3600*24)
185 'number_of_days_temp': round(diff_day)+1
189 'number_of_days_temp': 0
193 def onchange_sec_id(self, cr, uid, ids, status, context={}):
196 brows_obj = self.pool.get('hr.holidays.status').browse(cr, uid, [status])[0]
197 if brows_obj.categ_id and brows_obj.categ_id.section_id and not brows_obj.categ_id.section_id.allow_unlink:
199 'title': "Warning for ",
200 'message': "You won\'t be able to cancel this leave request because the CRM Section of the leave type disallows."
202 return {'warning': warning}
205 def set_to_draft(self, cr, uid, ids, *args):
206 self.write(cr, uid, ids, {
211 wf_service = netsvc.LocalService("workflow")
212 for holiday_id in ids:
213 wf_service.trg_create(uid, 'hr.holidays', holiday_id, cr)
216 def holidays_validate(self, cr, uid, ids, *args):
217 self.check_holidays(cr,uid,ids)
221 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
223 vals['manager_id'] = ids2[0]
225 raise osv.except_osv(_('Warning !'),_('Either there is no Employee defined, or no User attached with it.'))
226 self.write(cr, uid, ids, vals)
230 'date_from':record.date_from,
231 'date_to':record.date_to,
232 'calendar_id':record.employee_id.calendar_id.id,
233 'company_id':record.employee_id.company_id.id,
234 'resource_id':record.employee_id.resource_id.id
236 self.pool.get('resource.calendar.leaves').create(cr,uid,vals)
239 def holidays_confirm(self, cr, uid, ids, *args):
240 for record in self.browse(cr, uid, ids):
242 leave_asked = record.number_of_days_temp
243 if record.type == 'remove':
244 if record.employee_id and not record.holiday_status_id.limit:
245 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']
246 if leaves_rest < leave_asked:
247 raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for %s while available leaves are less than asked leaves.' %(record.employee_id.name)))
248 nb = -(record.number_of_days_temp)
250 nb = record.number_of_days_temp
251 if record.employee_id:
252 user_id = record.employee_id.user_id and record.employee_id.user_id.id or uid
254 self.write(cr, uid, [record.id], {
256 'number_of_days': nb,
261 def holidays_refuse(self, cr, uid, ids, *args):
265 ids2 = self.pool.get('hr.employee').search(cr, uid, [('user_id','=', uid)])
267 vals['manager_id'] = ids2[0]
268 self.write(cr, uid, ids, vals)
271 def holidays_cancel(self, cr, uid, ids, *args):
272 self._update_user_holidays(cr, uid, ids)
273 self.write(cr, uid, ids, {
278 def holidays_draft(self, cr, uid, ids, *args):
279 self.write(cr, uid, ids, {
284 def check_holidays(self,cr,uid,ids):
285 for record in self.browse(cr, uid, ids):
286 if not record.number_of_days:
287 raise osv.except_osv(_('Warning!'),_('Wrong leave definition.'))
288 if record.employee_id:
289 leave_asked = record.number_of_days
290 if leave_asked < 0.00:
291 if not record.holiday_status_id.limit:
292 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']
294 if leaves_rest < -(leave_asked):
295 raise osv.except_osv(_('Warning!'),_('You Cannot Validate leaves while available leaves are less than asked leaves.'))
299 'name' : record.name,
300 'holiday_status_id' : record.holiday_status_id.id,
302 'date_from' : record.date_from,
303 'date_to' : record.date_to,
304 'notes' : record.notes,
305 'number_of_days': record.number_of_days,
306 'number_of_days_temp': record.number_of_days_temp,
308 'allocation_type': record.allocation_type,
309 'parent_id': record.id,
311 employee_ids = self.pool.get('hr.employee').search(cr, uid, [])
312 for employee in employee_ids:
313 vals['employee_id'] = employee
314 user_id = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)])
316 vals['user_id'] = user_id[0]
317 holiday_ids.append(self.create(cr, uid, vals, context={}))
318 self.holidays_confirm(cr, uid, holiday_ids)
319 self.holidays_validate(cr, uid, holiday_ids)
321 if record.holiday_status_id.categ_id and record.date_from and record.date_to and record.employee_id:
323 vals['name']=record.name
324 vals['categ_id']=record.holiday_status_id.categ_id.id
325 epoch_c = time.mktime(time.strptime(record.date_to,'%Y-%m-%d %H:%M:%S'))
326 epoch_d = time.mktime(time.strptime(record.date_from,'%Y-%m-%d %H:%M:%S'))
327 diff_day = (epoch_c - epoch_d)/(3600*24)
328 vals['duration'] = (diff_day) * 8
329 vals['note'] = record.notes
330 vals['user_id'] = record.user_id.id
331 vals['date'] = record.date_from
332 case_id = self.pool.get('crm.meeting').create(cr,uid,vals)
333 self.write(cr, uid, ids, {'case_id':case_id})