Merge lp:~zaber/openobject-addons/timesheet-tz into lp:openobject-addons/6.1

Proposed by Don Kirkby
Status: Needs review
Proposed branch: lp:~zaber/openobject-addons/timesheet-tz
Merge into: lp:openobject-addons/6.1
Diff against target: 275 lines (+69/-37)
4 files modified
hr_attendance/hr_attendance.py (+7/-1)
hr_attendance/wizard/hr_attendance_sign_in_out.py (+2/-2)
hr_timesheet/wizard/hr_timesheet_sign_in_out.py (+3/-3)
hr_timesheet_sheet/hr_timesheet_sheet.py (+57/-31)
To merge this branch: bzr merge lp:~zaber/openobject-addons/timesheet-tz
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+141836@code.launchpad.net

Description of the change

This fixes bug lp:943091 in 6.1. There were several timezone problems, so I tried to fix each one in a separate commit.
There could still be problems if two users from different timezones edit the same timesheet, but the current 6.1 timesheet modules are unusable because of timezone problems, so I think this is a big improvement.

To post a comment you must log in.
7122. By Don Kirkby

[FIX] Use time zone for signing in and out of timesheets as part of bug lp:943091. Change hr_attendance.day field to use local date instead of UTC date.

7123. By Don Kirkby

[FIX] Use local time in total attendance calculation on timesheet as part of bug lp:943091.

7124. By Don Kirkby

[FIX] Store hours on hr_attendance because doing the timezone calculation in SQL was messy. Fixes timesheet by day view.

Unmerged revisions

7125. By Don Kirkby

[FIX] Use timezone in Sign in / sign out by project feature as part of bug lp:943091.

7124. By Don Kirkby

[FIX] Store hours on hr_attendance because doing the timezone calculation in SQL was messy. Fixes timesheet by day view.

7123. By Don Kirkby

[FIX] Use local time in total attendance calculation on timesheet as part of bug lp:943091.

7122. By Don Kirkby

[FIX] Use time zone for signing in and out of timesheets as part of bug lp:943091. Change hr_attendance.day field to use local date instead of UTC date.

7121. By Don Kirkby

[FIX] fix today button to use time zone as part of bug lp:943091.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hr_attendance/hr_attendance.py'
--- hr_attendance/hr_attendance.py 2012-01-31 13:36:57 +0000
+++ hr_attendance/hr_attendance.py 2013-01-03 23:59:21 +0000
@@ -20,6 +20,7 @@
20##############################################################################20##############################################################################
2121
22import time22import time
23from datetime import datetime
2324
24from osv import fields, osv25from osv import fields, osv
25from tools.translate import _26from tools.translate import _
@@ -48,7 +49,12 @@
48 def _day_compute(self, cr, uid, ids, fieldnames, args, context=None):49 def _day_compute(self, cr, uid, ids, fieldnames, args, context=None):
49 res = dict.fromkeys(ids, '')50 res = dict.fromkeys(ids, '')
50 for obj in self.browse(cr, uid, ids, context=context):51 for obj in self.browse(cr, uid, ids, context=context):
51 res[obj.id] = time.strftime('%Y-%m-%d', time.strptime(obj.name, '%Y-%m-%d %H:%M:%S'))52 timestamp = datetime.strptime(obj.name, '%Y-%m-%d %H:%M:%S')
53 res[obj.id] = fields.date.context_today(self,
54 cr,
55 uid,
56 context=context,
57 timestamp=timestamp)
52 return res58 return res
5359
54 _columns = {60 _columns = {
5561
=== modified file 'hr_attendance/wizard/hr_attendance_sign_in_out.py'
--- hr_attendance/wizard/hr_attendance_sign_in_out.py 2011-11-09 18:12:56 +0000
+++ hr_attendance/wizard/hr_attendance_sign_in_out.py 2013-01-03 23:59:21 +0000
@@ -162,7 +162,7 @@
162 self.pool.get('hr.attendance').create(cr, uid, {'name': data['last_time'], 'action': 'sign_out',162 self.pool.get('hr.attendance').create(cr, uid, {'name': data['last_time'], 'action': 'sign_out',
163 'employee_id': emp_id}, context=context)163 'employee_id': emp_id}, context=context)
164 try:164 try:
165 self.pool.get('hr.employee').attendance_action_change(cr, uid, [emp_id], 'sign_in')165 self.pool.get('hr.employee').attendance_action_change(cr, uid, [emp_id], 'sign_in', context=context)
166 except:166 except:
167 raise osv.except_osv(_('UserError'), _('A sign-in must be right after a sign-out !'))167 raise osv.except_osv(_('UserError'), _('A sign-in must be right after a sign-out !'))
168 return {'type': 'ir.actions.act_window_close'} # To do: Return Success message168 return {'type': 'ir.actions.act_window_close'} # To do: Return Success message
@@ -174,7 +174,7 @@
174 raise osv.except_osv(_('UserError'), _('The Sign-in date must be in the past'))174 raise osv.except_osv(_('UserError'), _('The Sign-in date must be in the past'))
175 self.pool.get('hr.attendance').create(cr, uid, {'name':data['last_time'], 'action':'sign_in', 'employee_id':emp_id}, context=context)175 self.pool.get('hr.attendance').create(cr, uid, {'name':data['last_time'], 'action':'sign_in', 'employee_id':emp_id}, context=context)
176 try:176 try:
177 self.pool.get('hr.employee').attendance_action_change(cr, uid, [emp_id], 'sign_out')177 self.pool.get('hr.employee').attendance_action_change(cr, uid, [emp_id], 'sign_out', context=context)
178 except:178 except:
179 raise osv.except_osv(_('UserError'), _('A sign-out must be right after a sign-in !'))179 raise osv.except_osv(_('UserError'), _('A sign-out must be right after a sign-in !'))
180 return {'type': 'ir.actions.act_window_close'} # To do: Return Success message180 return {'type': 'ir.actions.act_window_close'} # To do: Return Success message
181181
=== modified file 'hr_timesheet/wizard/hr_timesheet_sign_in_out.py'
--- hr_timesheet/wizard/hr_timesheet_sign_in_out.py 2011-12-21 10:52:14 +0000
+++ hr_timesheet/wizard/hr_timesheet_sign_in_out.py 2013-01-03 23:59:21 +0000
@@ -91,7 +91,7 @@
91 emp_obj = self.pool.get('hr.employee')91 emp_obj = self.pool.get('hr.employee')
92 for data in self.browse(cr, uid, ids, context=context):92 for data in self.browse(cr, uid, ids, context=context):
93 emp_id = data.emp_id.id93 emp_id = data.emp_id.id
94 emp_obj.attendance_action_change(cr, uid, [emp_id], type='sign_out', dt=data.date)94 emp_obj.attendance_action_change(cr, uid, [emp_id], type='sign_out', dt=data.date, context=context)
95 self._write(cr, uid, data, emp_id, context=context)95 self._write(cr, uid, data, emp_id, context=context)
96 return {'type': 'ir.actions.act_window_close'}96 return {'type': 'ir.actions.act_window_close'}
9797
@@ -99,7 +99,7 @@
99 emp_obj = self.pool.get('hr.employee')99 emp_obj = self.pool.get('hr.employee')
100 for data in self.browse(cr, uid, ids, context=context):100 for data in self.browse(cr, uid, ids, context=context):
101 emp_id = data.emp_id.id101 emp_id = data.emp_id.id
102 emp_obj.attendance_action_change(cr, uid, [emp_id], type='action', dt=data.date)102 emp_obj.attendance_action_change(cr, uid, [emp_id], type='action', dt=data.date, context=context)
103 self._write(cr, uid, data, emp_id, context=context)103 self._write(cr, uid, data, emp_id, context=context)
104 return {'type': 'ir.actions.act_window_close'}104 return {'type': 'ir.actions.act_window_close'}
105105
@@ -156,7 +156,7 @@
156 emp_obj = self.pool.get('hr.employee')156 emp_obj = self.pool.get('hr.employee')
157 for data in self.browse(cr, uid, ids, context=context):157 for data in self.browse(cr, uid, ids, context=context):
158 emp_id = data.emp_id.id158 emp_id = data.emp_id.id
159 emp_obj.attendance_action_change(cr, uid, [emp_id], type = 'sign_in' ,dt=data.date or False)159 emp_obj.attendance_action_change(cr, uid, [emp_id], type = 'sign_in' , context=context, dt=data.date or False)
160 return {'type': 'ir.actions.act_window_close'}160 return {'type': 'ir.actions.act_window_close'}
161161
162 def default_get(self, cr, uid, fields_list, context=None):162 def default_get(self, cr, uid, fields_list, context=None):
163163
=== modified file 'hr_timesheet_sheet/hr_timesheet_sheet.py'
--- hr_timesheet_sheet/hr_timesheet_sheet.py 2012-08-31 10:45:03 +0000
+++ hr_timesheet_sheet/hr_timesheet_sheet.py 2013-01-03 23:59:21 +0000
@@ -46,8 +46,8 @@
46 dom.insert(0 ,'|')46 dom.insert(0 ,'|')
47 dom.append('&')47 dom.append('&')
48 dom.append('&')48 dom.append('&')
49 dom.append(('name', '>=', res6[id]))49 dom.append(('day', '>=', res6[id]))
50 dom.append(('name', '<=', res6[id]))50 dom.append(('day', '<=', res6[id]))
51 dom.append(('sheet_id', '=', id))51 dom.append(('sheet_id', '=', id))
5252
53 ids2 = obj.pool.get(self._obj).search(cr, user, dom, limit=self._limit)53 ids2 = obj.pool.get(self._obj).search(cr, user, dom, limit=self._limit)
@@ -118,6 +118,10 @@
118 """118 """
119 context = context or {}119 context = context or {}
120 attendance_obj = self.pool.get('hr.attendance')120 attendance_obj = self.pool.get('hr.attendance')
121 now = fields.datetime.context_timestamp(cr,
122 uid,
123 datetime.now(),
124 context=context)
121 res = {}125 res = {}
122 for sheet_id in ids:126 for sheet_id in ids:
123 sheet = self.browse(cr, uid, sheet_id, context=context)127 sheet = self.browse(cr, uid, sheet_id, context=context)
@@ -129,25 +133,17 @@
129 total_attendance = {}133 total_attendance = {}
130 for attendance in [att for att in attendances134 for attendance in [att for att in attendances
131 if att.action in ('sign_in', 'sign_out')]:135 if att.action in ('sign_in', 'sign_out')]:
132 day = attendance.name[:10]136 day = attendance.day
133 if not total_attendance.get(day, False):137 if not total_attendance.get(day, False):
134 total_attendance[day] = timedelta(seconds=0)138 total_attendance[day] = timedelta(seconds=0)
135139
136 attendance_in_time = datetime.strptime(attendance.name, '%Y-%m-%d %H:%M:%S')140 total_attendance[day] += timedelta(hours=attendance.daily_hours)
137 attendance_interval = timedelta(hours=attendance_in_time.hour,
138 minutes=attendance_in_time.minute,
139 seconds=attendance_in_time.second)
140 if attendance.action == 'sign_in':
141 total_attendance[day] -= attendance_interval
142 else:
143 total_attendance[day] += attendance_interval
144141
145 # if the delta is negative, it means that a sign out is missing142 # if the delta is negative, it means that a sign out is missing
146 # in a such case, we want to have the time to the end of the day143 # in a such case, we want to have the time to the end of the day
147 # for a past date, and the time to now for the current date144 # for a past date, and the time to now for the current date
148 if total_attendance[day] < timedelta(0):145 if total_attendance[day] < timedelta(0):
149 if day == date_current:146 if day == date_current:
150 now = datetime.now()
151 total_attendance[day] += timedelta(hours=now.hour,147 total_attendance[day] += timedelta(hours=now.hour,
152 minutes=now.minute,148 minutes=now.minute,
153 seconds=now.second)149 seconds=now.second)
@@ -275,13 +271,15 @@
275 return True271 return True
276272
277 def date_today(self, cr, uid, ids, context=None):273 def date_today(self, cr, uid, ids, context=None):
274 today = fields.date.context_today(self, cr, uid, context=context)
275
278 for sheet in self.browse(cr, uid, ids, context=context):276 for sheet in self.browse(cr, uid, ids, context=context):
279 if datetime.today() <= datetime.strptime(sheet.date_from, '%Y-%m-%d'):277 if today <= sheet.date_from:
280 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context)278 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from}, context=context)
281 elif datetime.now() >= datetime.strptime(sheet.date_to, '%Y-%m-%d'):279 elif today >= sheet.date_to:
282 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context)280 self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to}, context=context)
283 else:281 else:
284 self.write(cr, uid, [sheet.id], {'date_current': time.strftime('%Y-%m-%d')}, context=context)282 self.write(cr, uid, [sheet.id], {'date_current': today}, context=context)
285 return True283 return True
286284
287 def date_previous(self, cr, uid, ids, context=None):285 def date_previous(self, cr, uid, ids, context=None):
@@ -314,7 +312,8 @@
314312
315 def check_sign(self, cr, uid, ids, typ, context=None):313 def check_sign(self, cr, uid, ids, typ, context=None):
316 sheet = self.browse(cr, uid, ids, context=context)[0]314 sheet = self.browse(cr, uid, ids, context=context)[0]
317 if not sheet.date_current == time.strftime('%Y-%m-%d'):315 today = fields.date.context_today(self, cr, uid, context=context)
316 if not sheet.date_current == today:
318 raise osv.except_osv(_('Error !'), _('You cannot sign in/sign out from an other date than today'))317 raise osv.except_osv(_('Error !'), _('You cannot sign in/sign out from an other date than today'))
319 return True318 return True
320319
@@ -477,7 +476,7 @@
477 context = {}476 context = {}
478 if 'date' in context:477 if 'date' in context:
479 return context['date']478 return context['date']
480 return time.strftime('%Y-%m-%d')479 return fields.date.context_today(self, cr, uid, context=context)
481480
482 def _sheet(self, cursor, user, ids, name, args, context=None):481 def _sheet(self, cursor, user, ids, name, args, context=None):
483 sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')482 sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
@@ -519,7 +518,7 @@
519 store={518 store={
520 'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),519 'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
521 'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10),520 'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10),
522 'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, ['line_id'], 10),521 'hr.analytic.timesheet': (lambda self,cr,uid,ids,context=None: ids, None, 10),
523 },522 },
524 ),523 ),
525 }524 }
@@ -573,8 +572,8 @@
573 INNER JOIN resource_resource r572 INNER JOIN resource_resource r
574 ON (e.resource_id = r.id)573 ON (e.resource_id = r.id)
575 ON (a.employee_id = e.id)574 ON (a.employee_id = e.id)
576 WHERE %(date_to)s >= date_trunc('day', a.name)575 WHERE %(date_to)s >= a.day
577 AND %(date_from)s <= a.name576 AND %(date_from)s <= a.day
578 AND %(user_id)s = r.user_id577 AND %(user_id)s = r.user_id
579 GROUP BY a.id""", {'date_from': ts.date_from,578 GROUP BY a.id""", {'date_from': ts.date_from,
580 'date_to': ts.date_to,579 'date_to': ts.date_to,
@@ -586,16 +585,38 @@
586 sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')585 sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
587 res = {}.fromkeys(ids, False)586 res = {}.fromkeys(ids, False)
588 for attendance in self.browse(cursor, user, ids, context=context):587 for attendance in self.browse(cursor, user, ids, context=context):
589 date_to = datetime.strftime(datetime.strptime(attendance.name[0:10], '%Y-%m-%d'), '%Y-%m-%d %H:%M:%S')588 timestamp = datetime.strptime(attendance.name, '%Y-%m-%d %H:%M:%S')
589 day = fields.date.context_today(self,
590 cursor,
591 user,
592 context=context,
593 timestamp=timestamp)
590 sheet_ids = sheet_obj.search(cursor, user,594 sheet_ids = sheet_obj.search(cursor, user,
591 [('date_to', '>=', date_to), ('date_from', '<=', attendance.name),595 [('date_to', '>=', day), ('date_from', '<=', day),
592 ('employee_id', '=', attendance.employee_id.id)],596 ('employee_id', '=', attendance.employee_id.id)],
593 context=context)597 context=context)
594 if sheet_ids:598 if sheet_ids:
595 # [0] because only one sheet possible for an employee between 2 dates599 # [0] because only one sheet possible for an employee between 2 dates
596 res[attendance.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]600 res[attendance.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
597 return res601 return res
598602
603 def _daily_hours(self, cr, uid, ids, name, args, context=None):
604 res = {}.fromkeys(ids, False)
605 for attendance in self.browse(cr, uid, ids, context=context):
606 day = attendance.day
607 attendance_in_time = fields.datetime.context_timestamp(
608 cr,
609 uid,
610 datetime.strptime(attendance.name, '%Y-%m-%d %H:%M:%S'),
611 context=context)
612 hours = (attendance_in_time.hour +
613 attendance_in_time.minute / 60.0 +
614 attendance_in_time.second / 3600.0)
615 if attendance.action == 'sign_in':
616 hours *= -1
617 res[attendance.id] = hours
618 return res
619
599 _columns = {620 _columns = {
600 'sheet_id': fields.function(_sheet, string='Sheet',621 'sheet_id': fields.function(_sheet, string='Sheet',
601 type='many2one', relation='hr_timesheet_sheet.sheet',622 type='many2one', relation='hr_timesheet_sheet.sheet',
@@ -603,7 +624,12 @@
603 'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),624 'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10),
604 'hr.attendance': (lambda self,cr,uid,ids,context=None: ids, ['employee_id', 'name', 'day'], 10),625 'hr.attendance': (lambda self,cr,uid,ids,context=None: ids, ['employee_id', 'name', 'day'], 10),
605 },626 },
606 )627 ),
628 'daily_hours': fields.function(_daily_hours,
629 type='float',
630 string='Daily Hours',
631 digits=(16,2),
632 store=True)
607 }633 }
608 _defaults = {634 _defaults = {
609 'name': _get_default_date,635 'name': _get_default_date,
@@ -709,10 +735,10 @@
709 ) union (735 ) union (
710 select736 select
711 -min(a.id) as id,737 -min(a.id) as id,
712 a.name::date as name,738 a.day::date as name,
713 s.id as sheet_id,739 s.id as sheet_id,
714 0.0 as total_timesheet,740 0.0 as total_timesheet,
715 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_attendance741 SUM(a.daily_hours)*60 as total_attendance
716 from742 from
717 hr_attendance a743 hr_attendance a
718 LEFT JOIN (hr_timesheet_sheet_sheet s744 LEFT JOIN (hr_timesheet_sheet_sheet s
@@ -721,10 +747,10 @@
721 ON (e.resource_id = r.id)747 ON (e.resource_id = r.id)
722 ON (s.user_id = r.user_id))748 ON (s.user_id = r.user_id))
723 ON (a.employee_id = e.id749 ON (a.employee_id = e.id
724 AND s.date_to >= date_trunc('day',a.name)750 AND s.date_to >= a.day::date
725 AND s.date_from <= a.name)751 AND s.date_from <= a.day::date)
726 WHERE action in ('sign_in', 'sign_out')752 WHERE action in ('sign_in', 'sign_out')
727 group by a.name::date, s.id753 group by a.day::date, s.id
728 )) AS foo754 )) AS foo
729 GROUP BY name, sheet_id755 GROUP BY name, sheet_id
730 )) AS bar""")756 )) AS bar""")