replace tabs with 4 spaces
[odoo/odoo.git] / addons / hr_timesheet_sheet / hr_timesheet_sheet.py
1 ##############################################################################
2 #
3 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
4 #
5 # $Id$
6 #
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
12 # Service Company
13 #
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 #
28 ##############################################################################
29
30 import time
31 from osv import fields
32 from osv import osv
33 import netsvc
34
35 from mx import DateTime
36 from tools.translate import _
37
38
39 class one2many_mod2(fields.one2many):
40     def get(self, cr, obj, ids, name, user=None, offset=0, context={}, values={}):
41         res = {}
42         for id in ids:
43             res[id] = []
44
45         res5 = obj.read(cr, user, ids, ['date_current', 'user_id'], context)
46         res6 = {}
47         for r in res5:
48             res6[r['id']] = (r['date_current'], r['user_id'][0])
49
50         ids2 = []
51         for id in ids:
52             dom = []
53             if id in res6:
54                 dom = [('name', '>=', res6[id][0] + ' 00:00:00'),
55                         ('name', '<=', res6[id][0] + ' 23:59:59'),
56                         ('employee_id.user_id', '=', res6[id][1])]
57             ids2.extend(obj.pool.get(self._obj).search(cr, user,
58                 dom, limit=self._limit))
59
60         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2,
61                 [self._fields_id], context=context, load='_classic_write'):
62             if r[self._fields_id]:
63                 res.setdefault(r[self._fields_id][0], []).append(r['id'])
64
65         return res
66
67     def set(self, cr, obj, id, field, values, user=None, context=None):
68         if context is None:
69             context = {}
70         context = context.copy()
71         context['sheet_id'] = id
72         return super(one2many_mod2, self).set(cr, obj, id, field, values, user=user,
73                 context=context)
74
75
76 class one2many_mod(fields.one2many):
77     def get(self, cr, obj, ids, name, user=None, offset=0, context={}, values={}):
78         res = {}
79         for id in ids:
80             res[id] = []
81
82         res5 = obj.read(cr, user, ids, ['date_current', 'user_id'], context)
83         res6 = {}
84         for r in res5:
85             res6[r['id']] = (r['date_current'], r['user_id'][0])
86
87         ids2 = []
88         for id in ids:
89             dom = []
90             if id in res6:
91                 dom = [('date', '=', res6[id][0]), ('user_id', '=', res6[id][1])]
92             ids2.extend(obj.pool.get(self._obj).search(cr, user,
93                 dom, limit=self._limit))
94
95         for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2,
96                 [self._fields_id], context=context, load='_classic_write'):
97             if r[self._fields_id]:
98                 res.setdefault(r[self._fields_id][0], []).append(r['id'])
99
100         return res
101
102 class hr_timesheet_sheet(osv.osv):
103     _name = "hr_timesheet_sheet.sheet"
104     _table = 'hr_timesheet_sheet_sheet'
105     _order = "id desc"
106
107     def _total_day(self, cr, uid, ids, name, args, context):
108         field_name = name.strip('_day')
109         cr.execute('SELECT sheet.id, day.' + field_name +' \
110                 FROM hr_timesheet_sheet_sheet AS sheet \
111                 LEFT JOIN hr_timesheet_sheet_sheet_day AS day \
112                     ON (sheet.id = day.sheet_id \
113                         AND day.name = sheet.date_current) \
114                 WHERE sheet.id in (' + ','.join([str(x) for x in ids]) + ')')
115         return dict(cr.fetchall())
116
117     def _total(self, cr, uid, ids, name, args, context):
118         cr.execute('SELECT s.id, COALESCE(SUM(d.' + name + '),0) \
119                 FROM hr_timesheet_sheet_sheet s \
120                     LEFT JOIN hr_timesheet_sheet_sheet_day d \
121                         ON (s.id = d.sheet_id) \
122                 WHERE s.id in ('+ ','.join(map(str, ids)) + ') \
123                 GROUP BY s.id')
124         return dict(cr.fetchall())
125
126     def _state_attendance(self, cr, uid, ids, name, args, context):
127         result = {}
128         link_emp = {}
129         emp_ids = []
130         emp_obj = self.pool.get('hr.employee')
131
132         for sheet in self.browse(cr, uid, ids, context):
133             result[sheet.id] = 'none'
134             emp_ids2 = emp_obj.search(cr, uid,
135                     [('user_id', '=', sheet.user_id.id)])
136             if emp_ids2:
137                 link_emp[emp_ids2[0]] = sheet.id
138                 emp_ids.append(emp_ids2[0])
139         for emp in emp_obj.browse(cr, uid, emp_ids, context=context):
140             if emp.id in link_emp:
141                 sheet_id = link_emp[emp.id]
142                 result[sheet_id] = emp.state
143         return result
144
145     def button_confirm(self, cr, uid, ids, context):
146         for sheet in self.browse(cr, uid, ids, context):
147             di = sheet.user_id.company_id.timesheet_max_difference
148             if (abs(sheet.total_difference) < di) or not di:
149                 wf_service = netsvc.LocalService("workflow")
150                 wf_service.trg_validate(uid, 'hr_timesheet_sheet.sheet', sheet.id, 'confirm', cr)
151             else:
152                 raise osv.except_osv(_('Warning !'), _('Please verify that the total difference of the sheet is lower than %.2f !') %(di,))
153         return True
154
155     def date_today(self, cr, uid, ids, context):
156         for sheet in self.browse(cr, uid, ids, context):
157             if DateTime.now() <= DateTime.strptime(sheet.date_from, '%Y-%m-%d'):
158                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,})
159             elif DateTime.now() >= DateTime.strptime(sheet.date_to, '%Y-%m-%d'):
160                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,})
161             else:
162                 self.write(cr, uid, [sheet.id], {'date_current': time.strftime('%Y-%m-%d')})
163         return True
164
165     def date_previous(self, cr, uid, ids, context):
166         for sheet in self.browse(cr, uid, ids, context):
167             if DateTime.strptime(sheet.date_current, '%Y-%m-%d') <= DateTime.strptime(sheet.date_from, '%Y-%m-%d'):
168                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,})
169             else:
170                 self.write(cr, uid, [sheet.id], {
171                     'date_current': (DateTime.strptime(sheet.date_current, '%Y-%m-%d') + DateTime.RelativeDateTime(days=-1)).strftime('%Y-%m-%d'),
172                 })
173         return True
174
175     def date_next(self, cr, uid, ids, context):
176         for sheet in self.browse(cr, uid, ids, context):
177             if DateTime.strptime(sheet.date_current, '%Y-%m-%d') >= DateTime.strptime(sheet.date_to, '%Y-%m-%d'):
178                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,})
179             else:
180                 self.write(cr, uid, [sheet.id], {
181                     'date_current': (DateTime.strptime(sheet.date_current, '%Y-%m-%d') + DateTime.RelativeDateTime(days=1)).strftime('%Y-%m-%d'),
182                 })
183         return True
184
185     def button_dummy(self, cr, uid, ids, context):
186         for sheet in self.browse(cr, uid, ids, context):
187             if DateTime.strptime(sheet.date_current, '%Y-%m-%d') <= DateTime.strptime(sheet.date_from, '%Y-%m-%d'):
188                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,})
189             elif DateTime.strptime(sheet.date_current, '%Y-%m-%d') >= DateTime.strptime(sheet.date_to, '%Y-%m-%d'):
190                 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,})
191         return True
192
193     def sign_in(self, cr, uid, ids, context):
194         if not self.browse(cr, uid, ids, context)[0].date_current == time.strftime('%Y-%m-%d'):
195             raise osv.except_osv(_('Error !'), _('You can not sign in from an other date than today'))
196         emp_obj = self.pool.get('hr.employee')
197         emp_id = emp_obj.search(cr, uid, [('user_id', '=', uid)])
198         context['sheet_id']=ids[0]
199         success = emp_obj.sign_in(cr, uid, emp_id, context=context)
200         return True
201
202     def sign_out(self, cr, uid, ids, context):
203         if not self.browse(cr, uid, ids, context)[0].date_current == time.strftime('%Y-%m-%d'):
204             raise osv.except_osv(_('Error !'), _('You can not sign out from an other date than today'))
205         emp_obj = self.pool.get('hr.employee')
206         emp_id = emp_obj.search(cr, uid, [('user_id', '=', uid)])
207         context['sheet_id']=ids[0]
208         success = emp_obj.sign_out(cr, uid, emp_id, context=context)
209         return True
210
211     _columns = {
212         'name': fields.char('Description', size=64, select=1),
213         'user_id': fields.many2one('res.users', 'User', required=True, select=1),
214         'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
215         'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
216         'date_current': fields.date('Current date', required=True),
217         'timesheet_ids' : one2many_mod('hr.analytic.timesheet', 'sheet_id',
218             'Timesheet lines', domain=[('date', '=', time.strftime('%Y-%m-%d'))],
219             readonly=True, states={
220                 'draft': [('readonly', False)],
221                 'new': [('readonly', False)]}
222             ),
223         'attendances_ids' : one2many_mod2('hr.attendance', 'sheet_id', 'Attendances', readonly=True, states={'draft':[('readonly',False)],'new':[('readonly',False)]}),
224         'state' : fields.selection([('new', 'New'),('draft','Draft'),('confirm','Confirmed'),('done','Done')], 'State', select=True, required=True, readonly=True),
225         'state_attendance' : fields.function(_state_attendance, method=True, type='selection', selection=[('absent', 'Absent'), ('present', 'Present'),('none','No employee defined')], string='Current state'),
226         'total_attendance_day': fields.function(_total_day, method=True, string='Total Attendance'),
227         'total_timesheet_day': fields.function(_total_day, method=True, string='Total Timesheet'),
228         'total_difference_day': fields.function(_total_day, method=True, string='Difference'),
229         'total_attendance': fields.function(_total, method=True, string='Total Attendance'),
230         'total_timesheet': fields.function(_total, method=True, string='Total Timesheet'),
231         'total_difference': fields.function(_total, method=True, string='Difference'),
232         'period_ids': fields.one2many('hr_timesheet_sheet.sheet.day', 'sheet_id', 'Period', readonly=True),
233         'account_ids': fields.one2many('hr_timesheet_sheet.sheet.account', 'sheet_id', 'Analytic accounts', readonly=True),
234     }
235
236     def _default_date_from(self,cr, uid, context={}):
237         user = self.pool.get('res.users').browse(cr, uid, uid, context)
238         r = user.company_id and user.company_id.timesheet_range or 'month'
239         if r=='month':
240             return time.strftime('%Y-%m-01')
241         elif r=='week':
242             return (DateTime.now() + DateTime.RelativeDateTime(weekday=(DateTime.Monday,0))).strftime('%Y-%m-%d')
243         elif r=='year':
244             return time.strftime('%Y-01-01')
245         return time.strftime('%Y-%m-%d')
246
247     def _default_date_to(self,cr, uid, context={}):
248         user = self.pool.get('res.users').browse(cr, uid, uid, context)
249         r = user.company_id and user.company_id.timesheet_range or 'month'
250         if r=='month':
251             return (DateTime.now() + DateTime.RelativeDateTime(months=+1,day=1,days=-1)).strftime('%Y-%m-%d')
252         elif r=='week':
253             return (DateTime.now() + DateTime.RelativeDateTime(weekday=(DateTime.Sunday,0))).strftime('%Y-%m-%d')
254         elif r=='year':
255             return time.strftime('%Y-12-31')
256         return time.strftime('%Y-%m-%d')
257
258     _defaults = {
259         'user_id': lambda self,cr,uid,c: uid,
260         'date_from' : _default_date_from,
261         'date_current' : lambda *a: time.strftime('%Y-%m-%d'),
262         'date_to' : _default_date_to,
263         'state': lambda *a: 'new',
264     }
265
266     def _sheet_date(self, cr, uid, ids):
267         for sheet in self.browse(cr, uid, ids):
268             cr.execute('SELECT id \
269                     FROM hr_timesheet_sheet_sheet \
270                     WHERE (date_from < %s and %s < date_to) \
271                         AND user_id=%d \
272                         AND id <> %d', (sheet.date_to, sheet.date_from,
273                             sheet.user_id.id, sheet.id))
274             if cr.fetchall():
275                 return False
276         return True
277
278     _constraints = [
279         (_sheet_date, 'You can not have 2 timesheets that overlaps !', ['date_from','date_to'])
280     ]
281
282     def action_set_to_draft(self, cr, uid, ids, *args):
283         self.write(cr, uid, ids, {'state': 'draft'})
284         wf_service = netsvc.LocalService('workflow')
285         for id in ids:
286             wf_service.trg_create(uid, self._name, id, cr)
287         return True
288
289     def name_get(self, cr, uid, ids, context={}):
290         if not len(ids):
291             return []
292         return [(r['id'], r['date_from'] + ' - ' + r['date_to']) \
293                 for r in self.read(cr, uid, ids, ['date_from', 'date_to'],
294                     context, load='_classic_write')]
295
296 hr_timesheet_sheet()
297
298
299 class hr_timesheet_line(osv.osv):
300     _inherit = "hr.analytic.timesheet"
301
302     def _get_default_date(self, cr, uid, context={}):
303         if 'date' in context:
304             return context['date']
305         return time.strftime('%Y-%m-%d')
306
307     def _sheet(self, cursor, user, ids, name, args, context):
308         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
309
310         cursor.execute('SELECT l.id, COALESCE(MAX(s.id), 0) \
311                 FROM hr_timesheet_sheet_sheet s \
312                     LEFT JOIN (hr_analytic_timesheet l \
313                         LEFT JOIN account_analytic_line al \
314                             ON (l.line_id = al.id)) \
315                         ON (s.date_to >= al.date \
316                             AND s.date_from <= al.date \
317                             AND s.user_id = al.user_id) \
318                 WHERE l.id in (' + ','.join([str(x) for x in ids]) + ') \
319                 GROUP BY l.id')
320         res = dict(cursor.fetchall())
321         sheet_names = {}
322         for sheet_id, name in sheet_obj.name_get(cursor, user, res.values(),
323                 context=context):
324             sheet_names[sheet_id] = name
325
326         for line_id in {}.fromkeys(ids):
327             sheet_id = res.get(line_id, False)
328             if sheet_id:
329                 res[line_id] = (sheet_id, sheet_names[sheet_id])
330             else:
331                 res[line_id] = False
332         return res
333
334     def _sheet_search(self, cursor, user, obj, name, args):
335         if not len(args):
336             return []
337         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
338
339         i = 0
340         while i < len(args):
341             fargs = args[i][0].split('.', 1)
342             if len(fargs) > 1:
343                 args[i] = (fargs[0], 'in', sheet_obj.search(cursor, user,
344                     [(fargs[1], args[i][1], args[i][2])]))
345                 i += 1
346                 continue
347             if isinstance(args[i][2], basestring):
348                 res_ids = sheet_obj.name_search(cursor, user, args[i][2], [],
349                         args[i][1])
350                 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
351             i += 1
352         qu1, qu2 = [], []
353         for x in args:
354             if x[1] != 'in':
355                 if (x[2] is False) and (x[1] == '='):
356                     qu1.append('(s.id IS NULL)')
357                 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
358                     qu1.append('(s.id IS NOT NULL)')
359                 else:
360                     qu1.append('(s.id %s %s)' % (x[1], '%d'))
361                     qu2.append(x[2])
362             elif x[1] == 'in':
363                 if len(x[2]) > 0:
364                     qu1.append('(s.id in (%s))' % (','.join(['%d'] * len(x[2]))))
365                     qu2 += x[2]
366                 else:
367                     qu1.append('(False)')
368         if len(qu1):
369             qu1 = ' WHERE ' + ' AND '.join(qu1)
370         else:
371             qu1 = ''
372         cursor.execute('SELECT l.id \
373                 FROM hr_timesheet_sheet_sheet s \
374                     LEFT JOIN (hr_analytic_timesheet l \
375                         LEFT JOIN account_analytic_line al \
376                             ON (l.line_id = al.id)) \
377                         ON (s.date_to >= al.date \
378                             AND s.date_from <= al.date \
379                             AND s.user_id = al.user_id)' + \
380                 qu1, qu2)
381         res = cursor.fetchall()
382         if not len(res):
383             return [('id', '=', '0')]
384         return [('id', 'in', [x[0] for x in res])]
385
386     _columns = {
387         'sheet_id': fields.function(_sheet, method=True, string='Sheet',
388             type='many2one', relation='hr_timesheet_sheet.sheet',
389             fnct_search=_sheet_search),
390     }
391     _defaults = {
392         'date': _get_default_date,
393     }
394
395     def create(self, cr, uid, vals, *args, **kwargs):
396         if 'sheet_id' in vals:
397             ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, vals['sheet_id'])
398             if not ts.state in ('draft', 'new'):
399                 raise osv.except_osv(_('Error !'), _('You can not modify an entry in a confirmed timesheet !'))
400         return super(hr_timesheet_line,self).create(cr, uid, vals, *args, **kwargs)
401
402     def unlink(self, cr, uid, ids, *args, **kwargs):
403         self._check(cr, uid, ids)
404         return super(hr_timesheet_line,self).unlink(cr, uid, ids,*args, **kwargs)
405
406     def write(self, cr, uid, ids, *args, **kwargs):
407         self._check(cr, uid, ids)
408         return super(hr_timesheet_line,self).write(cr, uid, ids,*args, **kwargs)
409
410     def _check(self, cr, uid, ids):
411         for att in self.browse(cr, uid, ids):
412             if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
413                 raise osv.except_osv(_('Error !'), _('You can not modify an entry in a confirmed timesheet !'))
414         return True
415
416 hr_timesheet_line()
417
418 class hr_attendance(osv.osv):
419     _inherit = "hr.attendance"
420
421     def _get_default_date(self, cr, uid, context={}):
422         if 'name' in context:
423             return context['name'] + time.strftime(' %H:%M:%S')
424         return time.strftime('%Y-%m-%d %H:%M:%S')
425
426     def _sheet(self, cursor, user, ids, name, args, context):
427         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
428
429         cursor.execute('SELECT a.id, COALESCE(MAX(s.id), 0) \
430                 FROM hr_timesheet_sheet_sheet s \
431                     LEFT JOIN (hr_attendance a \
432                         LEFT JOIN hr_employee e \
433                             ON (a.employee_id = e.id)) \
434                         ON (s.date_to >= a.name \
435                             AND s.date_from <= a.name \
436                             AND s.user_id = e.user_id) \
437                 WHERE a.id in (' + ','.join([str(x) for x in ids]) + ') \
438                 GROUP BY a.id')
439         res = dict(cursor.fetchall())
440         sheet_names = {}
441         for sheet_id, name in sheet_obj.name_get(cursor, user, res.values(),
442                 context=context):
443             sheet_names[sheet_id] = name
444
445         for line_id in {}.fromkeys(ids):
446             sheet_id = res.get(line_id, False)
447             if sheet_id:
448                 res[line_id] = (sheet_id, sheet_names[sheet_id])
449             else:
450                 res[line_id] = False
451         return res
452
453     def _sheet_search(self, cursor, user, obj, name, args):
454         if not len(args):
455             return []
456         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
457
458         i = 0
459         while i < len(args):
460             fargs = args[i][0].split('.', 1)
461             if len(fargs) > 1:
462                 args[i] = (fargs[0], 'in', sheet_obj.search(cursor, user,
463                     [(fargs[1], args[i][1], args[i][2])]))
464                 i += 1
465                 continue
466             if isinstance(args[i][2], basestring):
467                 res_ids = sheet_obj.name_search(cursor, user, args[i][2], [],
468                         args[i][1])
469                 args[i] = (args[i][0], 'in', [x[0] for x in res_ids])
470             i += 1
471         qu1, qu2 = [], []
472         for x in args:
473             if x[1] != 'in':
474                 if (x[2] is False) and (x[1] == '='):
475                     qu1.append('(s.id IS NULL)')
476                 elif (x[2] is False) and (x[1] == '<>' or x[1] == '!='):
477                     qu1.append('(s.id IS NOT NULL)')
478                 else:
479                     qu1.append('(s.id %s %s)' % (x[1], '%d'))
480                     qu2.append(x[2])
481             elif x[1] == 'in':
482                 if len(x[2]) > 0:
483                     qu1.append('(s.id in (%s))' % (','.join(['%d'] * len(x[2]))))
484                     qu2 += x[2]
485                 else:
486                     qu1.append('(False)')
487         if len(qu1):
488             qu1 = ' WHERE ' + ' AND '.join(qu1)
489         else:
490             qu1 = ''
491         cursor.execute('SELECT a.id\
492                 FROM hr_timesheet_sheet_sheet s \
493                     LEFT JOIN (hr_attendance a \
494                         LEFT JOIN hr_employee e \
495                             ON (a.employee_id = e.id)) \
496                         ON (s.date_to >= a.name \
497                             AND s.date_from <= a.name \
498                             AND s.user_id = e.user_id) ' + \
499                 qu1, qu2)
500         res = cursor.fetchall()
501         if not len(res):
502             return [('id', '=', '0')]
503         return [('id', 'in', [x[0] for x in res])]
504
505     _columns = {
506         'sheet_id': fields.function(_sheet, method=True, string='Sheet',
507             type='many2one', relation='hr_timesheet_sheet.sheet',
508             fnct_search=_sheet_search),
509     }
510     _defaults = {
511         'name': _get_default_date,
512     }
513
514     def create(self, cr, uid, vals, context={}):
515         if 'sheet_id' in context:
516             ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, context['sheet_id'])
517             if ts.state not in ('draft', 'new'):
518                 raise osv.except_osv(_('Error !'), _('You can not modify an entry in a confirmed timesheet !'))
519         res = super(hr_attendance,self).create(cr, uid, vals, context=context)
520         if 'sheet_id' in context:
521             if context['sheet_id'] != self.browse(cr, uid, res, context=context).sheet_id.id:
522                 raise osv.except_osv(_('UserError'), _('You can not enter an attendance ' \
523                         'date outside the current timesheet dates!'))
524         return res
525
526     def unlink(self, cr, uid, ids, *args, **kwargs):
527         self._check(cr, uid, ids)
528         return super(hr_attendance,self).unlink(cr, uid, ids,*args, **kwargs)
529
530     def write(self, cr, uid, ids, vals, context={}):
531         self._check(cr, uid, ids)
532         res = super(hr_attendance,self).write(cr, uid, ids, vals, context=context)
533         if 'sheet_id' in context:
534             for attendance in self.browse(cr, uid, ids, context=context):
535                 if context['sheet_id'] != attendance.sheet_id.id:
536                     raise osv.except_osv(_('UserError'), _('You can not enter an attendance ' \
537                             'date outside the current timesheet dates!'))
538         return res
539
540     def _check(self, cr, uid, ids):
541         for att in self.browse(cr, uid, ids):
542             if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
543                 raise osv.except_osv(_('Error !'), _('You can not modify an entry in a confirmed timesheet !'))
544         return True
545
546 hr_attendance()
547
548 class hr_timesheet_sheet_sheet_day(osv.osv):
549     _name = "hr_timesheet_sheet.sheet.day"
550     _description = "Timesheets by period"
551     _auto = False
552     _order='name'
553     _columns = {
554         'name': fields.date('Date', readonly=True),
555         'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True, select="1"),
556         'total_timesheet': fields.float('Project Timesheet', readonly=True),
557         'total_attendance': fields.float('Attendance', readonly=True),
558         'total_difference': fields.float('Difference', readonly=True),
559     }
560
561     def init(self, cr):
562         cr.execute("""create or replace view hr_timesheet_sheet_sheet_day as
563             SELECT
564                 id,
565                 name,
566                 sheet_id,
567                 total_timesheet,
568                 total_attendance,
569                 (total_attendance - total_timesheet) AS total_difference
570             FROM
571                 ((
572                     SELECT
573                         MAX(id) as id,
574                         name,
575                         sheet_id,
576                         SUM(total_timesheet) as total_timesheet,
577                         CASE WHEN SUM(total_attendance) < 0
578                             THEN (SUM(total_attendance) +
579                                 CASE WHEN current_date <> name
580                                     THEN 1440
581                                     ELSE (EXTRACT(hour FROM current_time) * 60) + EXTRACT(minute FROM current_time)
582                                 END
583                                 )
584                             ELSE SUM(total_attendance)
585                         END /60  as total_attendance
586                     FROM
587                         ((
588                             select
589                                 min(hrt.id) as id,
590                                 l.date::date as name,
591                                 s.id as sheet_id,
592                                 sum(l.unit_amount) as total_timesheet,
593                                 0.0 as total_attendance
594                             from
595                                 hr_analytic_timesheet hrt
596                                 left join (account_analytic_line l
597                                     LEFT JOIN hr_timesheet_sheet_sheet s
598                                     ON (s.date_to >= l.date
599                                         AND s.date_from <= l.date
600                                         AND s.user_id = l.user_id))
601                                     on (l.id = hrt.line_id)
602                             group by l.date::date, s.id
603                         ) union (
604                             select
605                                 -min(a.id) as id,
606                                 a.name::date as name,
607                                 s.id as sheet_id,
608                                 0.0 as total_timesheet,
609                                 SUM(((EXTRACT(hour FROM a.name) * 60) + EXTRACT(minute FROM a.name)) * (CASE WHEN a.action = 'sign_in' THEN -1 ELSE 1 END)) as total_attendance
610                             from
611                                 hr_attendance a
612                                 LEFT JOIN (hr_timesheet_sheet_sheet s
613                                     LEFT JOIN hr_employee e
614                                     ON (s.user_id = e.user_id))
615                                 ON (a.employee_id = e.id
616                                     AND s.date_to >= a.name
617                                     AND s.date_from <= a.name)
618                             WHERE action in ('sign_in', 'sign_out')
619                             group by a.name::date, s.id
620                         )) AS foo
621                         GROUP BY name, sheet_id
622                 )) AS bar""")
623
624 hr_timesheet_sheet_sheet_day()
625
626
627 class hr_timesheet_sheet_sheet_account(osv.osv):
628     _name = "hr_timesheet_sheet.sheet.account"
629     _description = "Timesheets by period"
630     _auto = False
631     _order='name'
632     _columns = {
633         'name': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True),
634         'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True),
635         'total': fields.float('Total Time', digits=(16,2), readonly=True),
636         'invoice_rate': fields.many2one('hr_timesheet_invoice.factor', 'Invoice rate', readonly=True),
637     }
638
639     def init(self, cr):
640         cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as (
641             select
642                 min(hrt.id) as id,
643                 l.account_id as name,
644                 s.id as sheet_id,
645                 sum(l.unit_amount) as total,
646                 l.to_invoice as invoice_rate
647             from
648                 hr_analytic_timesheet hrt
649                 left join (account_analytic_line l
650                     LEFT JOIN hr_timesheet_sheet_sheet s
651                         ON (s.date_to >= l.date
652                             AND s.date_from <= l.date
653                             AND s.user_id = l.user_id))
654                     on (l.id = hrt.line_id)
655             group by l.account_id, s.id, l.to_invoice
656         )""")
657
658 hr_timesheet_sheet_sheet_account()
659
660
661 class res_company(osv.osv):
662     _inherit = 'res.company'
663     _columns = {
664         'timesheet_range': fields.selection([('day','Day'),('week','Week'),('month','Month'),('year','Year')], 'Timeshet range'),
665         'timesheet_max_difference': fields.float('Timesheet allowed difference', help="Allowed difference between the sign in/out and the timesheet computation for one sheet. Set this to 0 if you do not want any control."),
666     }
667     _defaults = {
668         'timesheet_range': lambda *args: 'month',
669         'timesheet_max_difference': lambda *args: 0.0
670     }
671
672 res_company()