[IMP] hr_timesheet_sheet: use the new signal_xxx methods instead of trg_validate.
[odoo/odoo.git] / addons / hr_timesheet_sheet / hr_timesheet_sheet.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import time
23 from datetime import datetime, timedelta
24 from dateutil.relativedelta import relativedelta
25
26 from openerp.osv import fields, osv
27 from openerp.tools.translate import _
28 from openerp import netsvc
29
30 class hr_timesheet_sheet(osv.osv):
31     _name = "hr_timesheet_sheet.sheet"
32     _inherit = "mail.thread"
33     _table = 'hr_timesheet_sheet_sheet'
34     _order = "id desc"
35     _description="Timesheet"
36
37     def _total(self, cr, uid, ids, name, args, context=None):
38         """ Compute the attendances, analytic lines timesheets and differences between them
39             for all the days of a timesheet and the current day
40         """
41
42         res = {}
43         for sheet in self.browse(cr, uid, ids, context=context or {}):
44             res.setdefault(sheet.id, {
45                 'total_attendance': 0.0,
46                 'total_timesheet': 0.0,
47                 'total_difference': 0.0,
48             })
49             for period in sheet.period_ids:
50                 res[sheet.id]['total_attendance'] += period.total_attendance
51                 res[sheet.id]['total_timesheet'] += period.total_timesheet
52                 res[sheet.id]['total_difference'] += period.total_attendance - period.total_timesheet
53         return res
54
55     def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
56         ids_signin = self.pool.get('hr.attendance').search(cr,uid,[('sheet_id', '=', sheet_id),('action','=','sign_in')])
57         ids_signout = self.pool.get('hr.attendance').search(cr,uid,[('sheet_id', '=', sheet_id),('action','=','sign_out')])
58
59         if len(ids_signin) != len(ids_signout):
60             raise osv.except_osv(('Warning!'),_('The timesheet cannot be validated as it does not contain an equal number of sign ins and sign outs.'))
61         return True
62
63     def copy(self, cr, uid, ids, *args, **argv):
64         raise osv.except_osv(_('Error!'), _('You cannot duplicate a timesheet.'))
65
66     def create(self, cr, uid, vals, *args, **argv):
67         if 'employee_id' in vals:
68             if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id:
69                 raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
70             if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
71                 raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product, like \'Consultant\'.'))
72             if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
73                 raise osv.except_osv(_('Configuration Error!'), _('In order to create a timesheet for this employee, you must assign an analytic journal to the employee, like \'Timesheet Journal\'.'))
74         return super(hr_timesheet_sheet, self).create(cr, uid, vals, *args, **argv)
75
76     def write(self, cr, uid, ids, vals, *args, **argv):
77         if 'employee_id' in vals:
78             new_user_id = self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).user_id.id or False
79             if not new_user_id:
80                 raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
81             if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
82                 raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
83             if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
84                 raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product.'))
85             if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
86                 raise osv.except_osv(_('Configuration Error!'), _('In order to create a timesheet for this employee, you must assign an analytic journal to the employee, like \'Timesheet Journal\'.'))
87         return super(hr_timesheet_sheet, self).write(cr, uid, ids, vals, *args, **argv)
88
89     def button_confirm(self, cr, uid, ids, context=None):
90         for sheet in self.browse(cr, uid, ids, context=context):
91             if sheet.employee_id and sheet.employee_id.parent_id and sheet.employee_id.parent_id.user_id:
92                 self.message_subscribe_users(cr, uid, [sheet.id], user_ids=[sheet.employee_id.parent_id.user_id.id], context=context)
93             self.check_employee_attendance_state(cr, uid, sheet.id, context=context)
94             di = sheet.user_id.company_id.timesheet_max_difference
95             if (abs(sheet.total_difference) < di) or not di:
96                 self.signal_confirm(cr, uid, [sheet.id])
97             else:
98                 raise osv.except_osv(_('Warning!'), _('Please verify that the total difference of the sheet is lower than %.2f.') %(di,))
99         return True
100
101     def attendance_action_change(self, cr, uid, ids, context=None):
102         hr_employee = self.pool.get('hr.employee')
103         employee_ids = []
104         for sheet in self.browse(cr, uid, ids, context=context):
105             if sheet.employee_id.id not in employee_ids: employee_ids.append(sheet.employee_id.id)
106         return hr_employee.attendance_action_change(cr, uid, employee_ids, context=context)
107
108     _columns = {
109         'name': fields.char('Note', size=64, select=1,
110                             states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
111         'employee_id': fields.many2one('hr.employee', 'Employee', required=True),
112         'user_id': fields.related('employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True),#fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}),
113         'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
114         'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}),
115         'timesheet_ids' : fields.one2many('hr.analytic.timesheet', 'sheet_id',
116             'Timesheet lines',
117             readonly=True, states={
118                 'draft': [('readonly', False)],
119                 'new': [('readonly', False)]}
120             ),
121         'attendances_ids' : fields.one2many('hr.attendance', 'sheet_id', 'Attendances'),
122         'state' : fields.selection([
123             ('new', 'New'),
124             ('draft','Open'),
125             ('confirm','Waiting Approval'),
126             ('done','Approved')], 'Status', select=True, required=True, readonly=True,
127             help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed timesheet. \
128                 \n* The \'Confirmed\' status is used for to confirm the timesheet by user. \
129                 \n* The \'Done\' status is used when users timesheet is accepted by his/her senior.'),
130         'state_attendance' : fields.related('employee_id', 'state', type='selection', selection=[('absent', 'Absent'), ('present', 'Present')], string='Current Status', readonly=True),
131         'total_attendance': fields.function(_total, method=True, string='Total Attendance', multi="_total"),
132         'total_timesheet': fields.function(_total, method=True, string='Total Timesheet', multi="_total"),
133         'total_difference': fields.function(_total, method=True, string='Difference', multi="_total"),
134         'period_ids': fields.one2many('hr_timesheet_sheet.sheet.day', 'sheet_id', 'Period', readonly=True),
135         'account_ids': fields.one2many('hr_timesheet_sheet.sheet.account', 'sheet_id', 'Analytic accounts', readonly=True),
136         'company_id': fields.many2one('res.company', 'Company'),
137         'department_id':fields.many2one('hr.department','Department'),
138     }
139
140     def _default_date_from(self, cr, uid, context=None):
141         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
142         r = user.company_id and user.company_id.timesheet_range or 'month'
143         if r=='month':
144             return time.strftime('%Y-%m-01')
145         elif r=='week':
146             return (datetime.today() + relativedelta(weekday=0, days=-6)).strftime('%Y-%m-%d')
147         elif r=='year':
148             return time.strftime('%Y-01-01')
149         return time.strftime('%Y-%m-%d')
150
151     def _default_date_to(self, cr, uid, context=None):
152         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
153         r = user.company_id and user.company_id.timesheet_range or 'month'
154         if r=='month':
155             return (datetime.today() + relativedelta(months=+1,day=1,days=-1)).strftime('%Y-%m-%d')
156         elif r=='week':
157             return (datetime.today() + relativedelta(weekday=6)).strftime('%Y-%m-%d')
158         elif r=='year':
159             return time.strftime('%Y-12-31')
160         return time.strftime('%Y-%m-%d')
161
162     def _default_employee(self, cr, uid, context=None):
163         emp_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
164         return emp_ids and emp_ids[0] or False
165
166     _defaults = {
167         'date_from' : _default_date_from,
168         'date_to' : _default_date_to,
169         'state': 'new',
170         'employee_id': _default_employee,
171         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr_timesheet_sheet.sheet', context=c)
172     }
173
174     def _sheet_date(self, cr, uid, ids, forced_user_id=False, context=None):
175         for sheet in self.browse(cr, uid, ids, context=context):
176             new_user_id = forced_user_id or sheet.user_id and sheet.user_id.id
177             if new_user_id:
178                 cr.execute('SELECT id \
179                     FROM hr_timesheet_sheet_sheet \
180                     WHERE (date_from <= %s and %s <= date_to) \
181                         AND user_id=%s \
182                         AND id <> %s',(sheet.date_to, sheet.date_from, new_user_id, sheet.id))
183                 if cr.fetchall():
184                     return False
185         return True
186
187
188     _constraints = [
189         (_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
190     ]
191
192     def action_set_to_draft(self, cr, uid, ids, *args):
193         self.write(cr, uid, ids, {'state': 'draft'})
194         for id in ids:
195             self.create_workflow(cr, uid, [id])
196         return True
197
198     def name_get(self, cr, uid, ids, context=None):
199         if not ids:
200             return []
201         if isinstance(ids, (long, int)):
202             ids = [ids]
203         return [(r['id'], _('Week ')+datetime.strptime(r['date_from'], '%Y-%m-%d').strftime('%U')) \
204                 for r in self.read(cr, uid, ids, ['date_from'],
205                     context=context, load='_classic_write')]
206
207     def unlink(self, cr, uid, ids, context=None):
208         sheets = self.read(cr, uid, ids, ['state','total_attendance'], context=context)
209         for sheet in sheets:
210             if sheet['state'] in ('confirm', 'done'):
211                 raise osv.except_osv(_('Invalid Action!'), _('You cannot delete a timesheet which is already confirmed.'))
212             elif sheet['total_attendance'] <> 0.00:
213                 raise osv.except_osv(_('Invalid Action!'), _('You cannot delete a timesheet which have attendance entries.'))
214         return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context)
215
216     def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
217         department_id =  False
218         if employee_id:
219             empl_id = self.pool.get('hr.employee').browse(cr, uid, employee_id, context=context)
220             department_id = empl_id.department_id.id
221             user_id = empl_id.user_id.id
222         return {'value': {'department_id': department_id, 'user_id': user_id,}}
223
224     # ------------------------------------------------
225     # OpenChatter methods and notifications
226     # ------------------------------------------------
227
228     def _needaction_domain_get(self, cr, uid, context=None):
229         emp_obj = self.pool.get('hr.employee')
230         empids = emp_obj.search(cr, uid, [('parent_id.user_id', '=', uid)], context=context)
231         if not empids:
232             return False
233         dom = ['&', ('state', '=', 'confirm'), ('employee_id', 'in', empids)]
234         return dom
235
236
237 class account_analytic_line(osv.osv):
238     _inherit = "account.analytic.line"
239
240     def _get_default_date(self, cr, uid, context=None):
241         if context is None:
242             context = {}
243         #get the default date (should be: today)
244         res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
245         #if we got the dates from and to from the timesheet and if the default date is in between, we use the default
246         #but if the default isn't included in those dates, we use the date start of the timesheet as default
247         if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
248             if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
249                 return res
250             return context.get('timesheet_date_from')
251         #if we don't get the dates from the timesheet, we return the default value from super()
252         return res
253
254
255 class hr_timesheet_line(osv.osv):
256     _inherit = "hr.analytic.timesheet"
257
258     def _sheet(self, cursor, user, ids, name, args, context=None):
259         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
260         res = {}.fromkeys(ids, False)
261         for ts_line in self.browse(cursor, user, ids, context=context):
262             sheet_ids = sheet_obj.search(cursor, user,
263                 [('date_to', '>=', ts_line.date), ('date_from', '<=', ts_line.date),
264                  ('employee_id.user_id', '=', ts_line.user_id.id)],
265                 context=context)
266             if sheet_ids:
267             # [0] because only one sheet possible for an employee between 2 dates
268                 res[ts_line.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
269         return res
270
271     def _get_hr_timesheet_sheet(self, cr, uid, ids, context=None):
272         ts_line_ids = []
273         for ts in self.browse(cr, uid, ids, context=context):
274             cr.execute("""
275                     SELECT l.id
276                         FROM hr_analytic_timesheet l
277                     INNER JOIN account_analytic_line al
278                         ON (l.line_id = al.id)
279                     WHERE %(date_to)s >= al.date
280                         AND %(date_from)s <= al.date
281                         AND %(user_id)s = al.user_id
282                     GROUP BY l.id""", {'date_from': ts.date_from,
283                                         'date_to': ts.date_to,
284                                         'user_id': ts.employee_id.user_id.id,})
285             ts_line_ids.extend([row[0] for row in cr.fetchall()])
286         return ts_line_ids
287
288     def _get_account_analytic_line(self, cr, uid, ids, context=None):
289         ts_line_ids = self.pool.get('hr.analytic.timesheet').search(cr, uid, [('line_id', 'in', ids)])
290         return ts_line_ids
291
292     _columns = {
293         'sheet_id': fields.function(_sheet, string='Sheet',
294             type='many2one', relation='hr_timesheet_sheet.sheet', ondelete="cascade",
295             store={
296                     'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
297                     'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10),
298                     'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
299                   },
300             ),
301     }
302
303     def _check_sheet_state(self, cr, uid, ids, context=None):
304         if context is None:
305             context = {}
306         for timesheet_line in self.browse(cr, uid, ids, context=context):
307             if timesheet_line.sheet_id and timesheet_line.sheet_id.state not in ('draft', 'new'):
308                 return False
309         return True
310
311     _constraints = [
312         (_check_sheet_state, 'You cannot modify an entry in a Confirmed/Done timesheet !', ['state']),
313     ]
314
315     def unlink(self, cr, uid, ids, *args, **kwargs):
316         if isinstance(ids, (int, long)):
317             ids = [ids]
318         self._check(cr, uid, ids)
319         return super(hr_timesheet_line,self).unlink(cr, uid, ids,*args, **kwargs)
320
321     def _check(self, cr, uid, ids):
322         for att in self.browse(cr, uid, ids):
323             if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
324                 raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.'))
325         return True
326
327     def multi_on_change_account_id(self, cr, uid, ids, account_ids, context=None):
328         return dict([(el, self.on_change_account_id(cr, uid, ids, el, context.get('user_id', uid))) for el in account_ids])
329
330
331 hr_timesheet_line()
332
333 class hr_attendance(osv.osv):
334     _inherit = "hr.attendance"
335
336     def _get_default_date(self, cr, uid, context=None):
337         if context is None:
338             context = {}
339         if 'name' in context:
340             return context['name'] + time.strftime(' %H:%M:%S')
341         return time.strftime('%Y-%m-%d %H:%M:%S')
342
343     def _get_hr_timesheet_sheet(self, cr, uid, ids, context=None):
344         attendance_ids = []
345         for ts in self.browse(cr, uid, ids, context=context):
346             cr.execute("""
347                         SELECT a.id
348                           FROM hr_attendance a
349                          INNER JOIN hr_employee e
350                                INNER JOIN resource_resource r
351                                        ON (e.resource_id = r.id)
352                             ON (a.employee_id = e.id)
353                         WHERE %(date_to)s >= date_trunc('day', a.name)
354                               AND %(date_from)s <= a.name
355                               AND %(user_id)s = r.user_id
356                          GROUP BY a.id""", {'date_from': ts.date_from,
357                                             'date_to': ts.date_to,
358                                             'user_id': ts.employee_id.user_id.id,})
359             attendance_ids.extend([row[0] for row in cr.fetchall()])
360         return attendance_ids
361
362     def _sheet(self, cursor, user, ids, name, args, context=None):
363         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
364         res = {}.fromkeys(ids, False)
365         for attendance in self.browse(cursor, user, ids, context=context):
366             date_to = datetime.strftime(datetime.strptime(attendance.name[0:10], '%Y-%m-%d'), '%Y-%m-%d %H:%M:%S')
367             sheet_ids = sheet_obj.search(cursor, user,
368                 [('date_to', '>=', date_to), ('date_from', '<=', attendance.name),
369                  ('employee_id', '=', attendance.employee_id.id)],
370                 context=context)
371             if sheet_ids:
372                 # [0] because only one sheet possible for an employee between 2 dates
373                 res[attendance.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
374         return res
375
376     _columns = {
377         'sheet_id': fields.function(_sheet, string='Sheet',
378             type='many2one', relation='hr_timesheet_sheet.sheet',
379             store={
380                       'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
381                       'hr.attendance': (lambda self,cr,uid,ids,context=None: ids, ['employee_id', 'name', 'day'], 10),
382                   },
383             )
384     }
385     _defaults = {
386         'name': _get_default_date,
387     }
388
389     def create(self, cr, uid, vals, context=None):
390         if context is None:
391             context = {}
392         if 'sheet_id' in context:
393             ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, context['sheet_id'], context=context)
394             if ts.state not in ('draft', 'new'):
395                 raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.'))
396         res = super(hr_attendance,self).create(cr, uid, vals, context=context)
397         if 'sheet_id' in context:
398             if context['sheet_id'] != self.browse(cr, uid, res, context=context).sheet_id.id:
399                 raise osv.except_osv(_('User Error!'), _('You cannot enter an attendance ' \
400                         'date outside the current timesheet dates.'))
401         return res
402
403     def unlink(self, cr, uid, ids, *args, **kwargs):
404         if isinstance(ids, (int, long)):
405             ids = [ids]
406         self._check(cr, uid, ids)
407         return super(hr_attendance,self).unlink(cr, uid, ids,*args, **kwargs)
408
409     def write(self, cr, uid, ids, vals, context=None):
410         if context is None:
411             context = {}
412         if isinstance(ids, (int, long)):
413             ids = [ids]
414         self._check(cr, uid, ids)
415         res = super(hr_attendance,self).write(cr, uid, ids, vals, context=context)
416         if 'sheet_id' in context:
417             for attendance in self.browse(cr, uid, ids, context=context):
418                 if context['sheet_id'] != attendance.sheet_id.id:
419                     raise osv.except_osv(_('User Error!'), _('You cannot enter an attendance ' \
420                             'date outside the current timesheet dates.'))
421         return res
422
423     def _check(self, cr, uid, ids):
424         for att in self.browse(cr, uid, ids):
425             if att.sheet_id and att.sheet_id.state not in ('draft', 'new'):
426                 raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet'))
427         return True
428
429 hr_attendance()
430
431 class hr_timesheet_sheet_sheet_day(osv.osv):
432     _name = "hr_timesheet_sheet.sheet.day"
433     _description = "Timesheets by Period"
434     _auto = False
435     _order='name'
436     _columns = {
437         'name': fields.date('Date', readonly=True),
438         'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True, select="1"),
439         'total_timesheet': fields.float('Total Timesheet', readonly=True),
440         'total_attendance': fields.float('Attendance', readonly=True),
441         'total_difference': fields.float('Difference', readonly=True),
442     }
443
444     def init(self, cr):
445         cr.execute("""create or replace view hr_timesheet_sheet_sheet_day as
446             SELECT
447                 id,
448                 name,
449                 sheet_id,
450                 total_timesheet,
451                 total_attendance,
452                 cast(round(cast(total_attendance - total_timesheet as Numeric),2) as Double Precision) AS total_difference
453             FROM
454                 ((
455                     SELECT
456                         MAX(id) as id,
457                         name,
458                         sheet_id,
459                         SUM(total_timesheet) as total_timesheet,
460                         CASE WHEN SUM(total_attendance) < 0
461                             THEN (SUM(total_attendance) +
462                                 CASE WHEN current_date <> name
463                                     THEN 1440
464                                     ELSE (EXTRACT(hour FROM current_time) * 60) + EXTRACT(minute FROM current_time)
465                                 END
466                                 )
467                             ELSE SUM(total_attendance)
468                         END /60  as total_attendance
469                     FROM
470                         ((
471                             select
472                                 min(hrt.id) as id,
473                                 l.date::date as name,
474                                 s.id as sheet_id,
475                                 sum(l.unit_amount) as total_timesheet,
476                                 0.0 as total_attendance
477                             from
478                                 hr_analytic_timesheet hrt
479                                 left join (account_analytic_line l
480                                     LEFT JOIN hr_timesheet_sheet_sheet s
481                                     ON (s.date_to >= l.date
482                                         AND s.date_from <= l.date
483                                         AND s.user_id = l.user_id))
484                                     on (l.id = hrt.line_id)
485                             group by l.date::date, s.id
486                         ) union (
487                             select
488                                 -min(a.id) as id,
489                                 a.name::date as name,
490                                 s.id as sheet_id,
491                                 0.0 as total_timesheet,
492                                 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
493                             from
494                                 hr_attendance a
495                                 LEFT JOIN (hr_timesheet_sheet_sheet s
496                                     LEFT JOIN resource_resource r
497                                         LEFT JOIN hr_employee e
498                                         ON (e.resource_id = r.id)
499                                     ON (s.user_id = r.user_id))
500                                 ON (a.employee_id = e.id
501                                     AND s.date_to >= date_trunc('day',a.name)
502                                     AND s.date_from <= a.name)
503                             WHERE action in ('sign_in', 'sign_out')
504                             group by a.name::date, s.id
505                         )) AS foo
506                         GROUP BY name, sheet_id
507                 )) AS bar""")
508
509 hr_timesheet_sheet_sheet_day()
510
511
512 class hr_timesheet_sheet_sheet_account(osv.osv):
513     _name = "hr_timesheet_sheet.sheet.account"
514     _description = "Timesheets by Period"
515     _auto = False
516     _order='name'
517     _columns = {
518         'name': fields.many2one('account.analytic.account', 'Project / Analytic Account', readonly=True),
519         'sheet_id': fields.many2one('hr_timesheet_sheet.sheet', 'Sheet', readonly=True),
520         'total': fields.float('Total Time', digits=(16,2), readonly=True),
521         'invoice_rate': fields.many2one('hr_timesheet_invoice.factor', 'Invoice rate', readonly=True),
522         }
523
524     def init(self, cr):
525         cr.execute("""create or replace view hr_timesheet_sheet_sheet_account as (
526             select
527                 min(hrt.id) as id,
528                 l.account_id as name,
529                 s.id as sheet_id,
530                 sum(l.unit_amount) as total,
531                 l.to_invoice as invoice_rate
532             from
533                 hr_analytic_timesheet hrt
534                 left join (account_analytic_line l
535                     LEFT JOIN hr_timesheet_sheet_sheet s
536                         ON (s.date_to >= l.date
537                             AND s.date_from <= l.date
538                             AND s.user_id = l.user_id))
539                     on (l.id = hrt.line_id)
540             group by l.account_id, s.id, l.to_invoice
541         )""")
542
543 hr_timesheet_sheet_sheet_account()
544
545
546
547 class res_company(osv.osv):
548     _inherit = 'res.company'
549     _columns = {
550         'timesheet_range': fields.selection(
551             [('day','Day'),('week','Week'),('month','Month')], 'Timesheet range',
552             help="Periodicity on which you validate your timesheets."),
553         'timesheet_max_difference': fields.float('Timesheet allowed difference(Hours)',
554             help="Allowed difference in hours between the sign in/out and the timesheet " \
555                  "computation for one sheet. Set this to 0 if you do not want any control."),
556     }
557     _defaults = {
558         'timesheet_range': lambda *args: 'week',
559         'timesheet_max_difference': lambda *args: 0.0
560     }
561
562 res_company()
563
564 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
565