Merge lp:~elbati/domsense-agilebg-addons/6.1_hr_attendance_analysis_ref into lp:domsense-agilebg-addons/6.1

Proposed by Lorenzo Battistini
Status: Needs review
Proposed branch: lp:~elbati/domsense-agilebg-addons/6.1_hr_attendance_analysis_ref
Merge into: lp:domsense-agilebg-addons/6.1
Diff against target: 274 lines (+116/-111)
1 file modified
hr_attendance_analysis/hr_attendance.py (+116/-111)
To merge this branch: bzr merge lp:~elbati/domsense-agilebg-addons/6.1_hr_attendance_analysis_ref
Reviewer Review Type Date Requested Status
Agile Business Group Pending
Review via email: mp+174814@code.launchpad.net

Commit message

Fixes:
 - time_difference: allow small (< precision) time differences
 - if no next_attendance_ids found, avoid useless computations
 - consider attendances of type 'action' while searching for previous sign in

To post a comment you must log in.
269. By Lorenzo Battistini

[add] from __future__ import division

Unmerged revisions

269. By Lorenzo Battistini

[add] from __future__ import division

268. By Lorenzo Battistini

[REF]

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hr_attendance_analysis/hr_attendance.py'
2--- hr_attendance_analysis/hr_attendance.py 2013-01-04 14:00:01 +0000
3+++ hr_attendance_analysis/hr_attendance.py 2013-07-16 07:07:29 +0000
4@@ -19,6 +19,7 @@
5 #
6 ##############################################################################
7
8+from __future__ import division
9 from osv import fields, osv
10 from tools.translate import _
11 import time
12@@ -69,10 +70,11 @@
13 str_time = self.float_time_convert(float_val)
14 return timedelta(0, int(str_time.split(':')[0]) * 60.0*60.0 + int(str_time.split(':')[1]) * 60.0)
15
16- def time_difference(self, float_start_time, float_end_time):
17+ def time_difference(self, float_start_time, float_end_time, precision=(1.0/60)):
18 if float_end_time < float_start_time:
19- raise osv.except_osv(_('Error'), _('End time %s < start time %s')
20- % (str(float_end_time),str(float_start_time)))
21+ if float_start_time - float_end_time > precision:
22+ raise osv.except_osv(_('Error'), _('End time %s < start time %s')
23+ % (str(float_end_time),str(float_start_time)))
24 delta = self.float_to_datetime(float_end_time) - self.float_to_datetime(float_start_time)
25 return delta.total_seconds() / 60.0 / 60.0
26
27@@ -170,6 +172,7 @@
28 # 2012.10.16 LF FIX : Attendance in context timezone
29 attendance_start = datetime.strptime(attendance.name, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc).astimezone(active_tz)
30 next_attendance_date = str_now
31+ next_attendance_ids = False
32 # should we compute for sign out too?
33 if attendance.action == 'sign_in':
34 next_attendance_ids = self.search(cr, uid, [
35@@ -181,121 +184,121 @@
36 # 2012.10.16 LF FIX : Attendance in context timezone
37 raise osv.except_osv(_('Error'), _('Incongruent data: sign-in %s is followed by another sign-in') % attendance_start)
38 next_attendance_date = next_attendance.name
39- # 2012.10.16 LF FIX : Attendance in context timezone
40- attendance_stop = datetime.strptime(next_attendance_date, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc).astimezone(active_tz)
41- duration_delta = attendance_stop - attendance_start
42- duration = duration_delta.total_seconds() / 60.0 / 60.0
43- duration = round(duration / precision) * precision
44+ # 2012.10.16 LF FIX : Attendance in context timezone
45+ attendance_stop = datetime.strptime(next_attendance_date, '%Y-%m-%d %H:%M:%S').replace(tzinfo=pytz.utc).astimezone(active_tz)
46+ duration_delta = attendance_stop - attendance_start
47+ duration = duration_delta.total_seconds() / 60.0 / 60.0
48+ duration = round(duration / precision) * precision
49 res[attendance.id]['duration'] = duration
50 res[attendance.id]['end_datetime'] = next_attendance_date
51 # If contract is not specified: working days = 24/7
52 res[attendance.id]['inside_calendar_duration'] = duration
53 res[attendance.id]['outside_calendar_duration'] = 0.0
54
55- active_contract_ids = self.get_active_contracts(cr, uid, attendance.employee_id.id, date=str_now[:10])
56+ if next_attendance_ids:
57+ active_contract_ids = self.get_active_contracts(cr, uid, attendance.employee_id.id, date=str_now[:10])
58
59- if active_contract_ids:
60- contract = contract_pool.browse(cr, uid, active_contract_ids[0])
61- if contract.working_hours:
62- # TODO applicare prima arrotondamento o tolleranza?
63- if contract.working_hours.attendance_rounding:
64- float_attendance_rounding = float(contract.working_hours.attendance_rounding)
65- rounded_start_hour = self._ceil_rounding(float_attendance_rounding, attendance_start)
66- rounded_stop_hour = self._floor_rounding(float_attendance_rounding, attendance_stop)
67-
68- if abs(1- rounded_start_hour) < 0.01: # if shift == 1 hour
69- attendance_start = datetime(attendance_start.year, attendance_start.month,
70- attendance_start.day, attendance_start.hour + 1)
71- else:
72- attendance_start = datetime(attendance_start.year, attendance_start.month,
73- attendance_start.day, attendance_start.hour, int(round(rounded_start_hour * 60.0)))
74+ if active_contract_ids:
75+ contract = contract_pool.browse(cr, uid, active_contract_ids[0])
76+ if contract.working_hours:
77+ # TODO applicare prima arrotondamento o tolleranza?
78+ if contract.working_hours.attendance_rounding:
79+ float_attendance_rounding = float(contract.working_hours.attendance_rounding)
80+ rounded_start_hour = self._ceil_rounding(float_attendance_rounding, attendance_start)
81+ rounded_stop_hour = self._floor_rounding(float_attendance_rounding, attendance_stop)
82
83- attendance_stop = datetime(attendance_stop.year, attendance_stop.month,
84- attendance_stop.day, attendance_stop.hour, int(round(rounded_stop_hour * 60.0)))
85-
86- # again
87- duration_delta = attendance_stop - attendance_start
88- duration = duration_delta.total_seconds() / 60.0 / 60.0
89- duration = round(duration / precision) * precision
90- res[attendance.id]['duration'] = duration
91-
92- res[attendance.id]['inside_calendar_duration'] = 0.0
93- res[attendance.id]['outside_calendar_duration'] = 0.0
94- calendar_id = contract.working_hours.id
95- intervals_within = 0
96-
97- # split attendance in intervals = precision
98- # 2012.10.16 LF FIX : no recursion in split attendance
99- splitted_attendances = self._split_norecurse_attendance(attendance_start, duration, precision)
100- counter = 0
101- for atomic_attendance in splitted_attendances:
102- counter += 1
103- centered_attendance = atomic_attendance[0] + timedelta(0,0,0,0,0, atomic_attendance[1] / 2.0)
104- centered_attendance_hour = centered_attendance.hour + centered_attendance.minute / 60.0 \
105- + centered_attendance.second / 60.0 / 60.0
106- # check if centered_attendance is within a working schedule
107- # 2012.10.16 LF FIX : weekday must be single character not int
108- weekday_char = str(unichr(centered_attendance.weekday() + 48))
109- matched_schedule_ids = attendance_pool.search(cr, uid, [
110- '&',
111- '|',
112- ('date_from', '=', False),
113- ('date_from','<=',centered_attendance.date()),
114- '|',
115- ('dayofweek', '=', False),
116- ('dayofweek','=',weekday_char),
117- ('calendar_id','=',calendar_id),
118- ('hour_to','>=',centered_attendance_hour),
119- ('hour_from','<=',centered_attendance_hour),
120- ])
121- if len(matched_schedule_ids) > 1:
122- raise osv.except_osv(_('Error'),
123- _('Wrongly configured working schedule with id %s') % str(calendar_id))
124- if matched_schedule_ids:
125- intervals_within += 1
126- # sign in tolerance
127- if intervals_within == 1:
128- calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
129- attendance_start_hour = attendance_start.hour + attendance_start.minute / 60.0 \
130- + attendance_start.second / 60.0 / 60.0
131- if attendance_start_hour >= calendar_attendance.hour_from and \
132- (attendance_start_hour - (calendar_attendance.hour_from +
133- calendar_attendance.tolerance_to)) < 0.01: # handling float roundings (<=)
134- additional_intervals = round(
135- (attendance_start_hour - calendar_attendance.hour_from) / precision)
136- intervals_within += additional_intervals
137- res[attendance.id]['duration'] = self.time_sum(
138- res[attendance.id]['duration'], additional_intervals * precision)
139- # sign out tolerance
140- if len(splitted_attendances) == counter:
141- attendance_stop_hour = attendance_stop.hour + attendance_stop.minute / 60.0 \
142- + attendance_stop.second / 60.0 / 60.0
143- calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
144- if attendance_stop_hour <= calendar_attendance.hour_to and \
145- (attendance_stop_hour - (calendar_attendance.hour_to -
146- calendar_attendance.tolerance_from)) > -0.01: # handling float roundings (>=)
147- additional_intervals = round(
148- (calendar_attendance.hour_to - attendance_stop_hour) / precision)
149- intervals_within += additional_intervals
150- res[attendance.id]['duration'] = self.time_sum(
151- res[attendance.id]['duration'], additional_intervals * precision)
152-
153- res[attendance.id]['inside_calendar_duration'] = intervals_within * precision
154- # make difference using time in order to avoid rounding errors
155- # inside_calendar_duration can't be > duration
156- res[attendance.id]['outside_calendar_duration'] = self.time_difference(
157- res[attendance.id]['inside_calendar_duration'], res[attendance.id]['duration'])
158-
159- if contract.working_hours.overtime_rounding:
160- if res[attendance.id]['outside_calendar_duration']:
161- overtime = res[attendance.id]['outside_calendar_duration']
162- if contract.working_hours.overtime_rounding_tolerance:
163- overtime = self.time_sum(overtime,
164- contract.working_hours.overtime_rounding_tolerance)
165- float_overtime_rounding = float(contract.working_hours.overtime_rounding)
166- res[attendance.id]['outside_calendar_duration'] = math.floor(
167- overtime * float_overtime_rounding) / float_overtime_rounding
168-
169+ if abs(1- rounded_start_hour) < 0.01: # if shift == 1 hour
170+ attendance_start = datetime(attendance_start.year, attendance_start.month,
171+ attendance_start.day, attendance_start.hour + 1)
172+ else:
173+ attendance_start = datetime(attendance_start.year, attendance_start.month,
174+ attendance_start.day, attendance_start.hour, int(round(rounded_start_hour * 60.0)))
175+
176+ attendance_stop = datetime(attendance_stop.year, attendance_stop.month,
177+ attendance_stop.day, attendance_stop.hour, int(round(rounded_stop_hour * 60.0)))
178+
179+ # again
180+ duration_delta = attendance_stop - attendance_start
181+ duration = duration_delta.total_seconds() / 60.0 / 60.0
182+ duration = round(duration / precision) * precision
183+ res[attendance.id]['duration'] = duration
184+
185+ res[attendance.id]['inside_calendar_duration'] = 0.0
186+ res[attendance.id]['outside_calendar_duration'] = 0.0
187+ calendar_id = contract.working_hours.id
188+ intervals_within = 0
189+
190+ # split attendance in intervals = precision
191+ # 2012.10.16 LF FIX : no recursion in split attendance
192+ splitted_attendances = self._split_norecurse_attendance(attendance_start, duration, precision)
193+ counter = 0
194+ for atomic_attendance in splitted_attendances:
195+ counter += 1
196+ centered_attendance = atomic_attendance[0] + timedelta(0,0,0,0,0, atomic_attendance[1] / 2.0)
197+ centered_attendance_hour = centered_attendance.hour + centered_attendance.minute / 60.0 \
198+ + centered_attendance.second / 60.0 / 60.0
199+ # check if centered_attendance is within a working schedule
200+ # 2012.10.16 LF FIX : weekday must be single character not int
201+ weekday_char = str(unichr(centered_attendance.weekday() + 48))
202+ matched_schedule_ids = attendance_pool.search(cr, uid, [
203+ '&',
204+ '|',
205+ ('date_from', '=', False),
206+ ('date_from','<=',centered_attendance.date()),
207+ '|',
208+ ('dayofweek', '=', False),
209+ ('dayofweek','=',weekday_char),
210+ ('calendar_id','=',calendar_id),
211+ ('hour_to','>=',centered_attendance_hour),
212+ ('hour_from','<=',centered_attendance_hour),
213+ ])
214+ if len(matched_schedule_ids) > 1:
215+ raise osv.except_osv(_('Error'),
216+ _('Wrongly configured working schedule with id %s') % str(calendar_id))
217+ if matched_schedule_ids:
218+ intervals_within += 1
219+ # sign in tolerance
220+ if intervals_within == 1:
221+ calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
222+ attendance_start_hour = attendance_start.hour + attendance_start.minute / 60.0 \
223+ + attendance_start.second / 60.0 / 60.0
224+ if attendance_start_hour >= calendar_attendance.hour_from and \
225+ (attendance_start_hour - (calendar_attendance.hour_from +
226+ calendar_attendance.tolerance_to)) < 0.01: # handling float roundings (<=)
227+ additional_intervals = round(
228+ (attendance_start_hour - calendar_attendance.hour_from) / precision)
229+ intervals_within += additional_intervals
230+ res[attendance.id]['duration'] = self.time_sum(
231+ res[attendance.id]['duration'], additional_intervals * precision)
232+ # sign out tolerance
233+ if len(splitted_attendances) == counter:
234+ attendance_stop_hour = attendance_stop.hour + attendance_stop.minute / 60.0 \
235+ + attendance_stop.second / 60.0 / 60.0
236+ calendar_attendance = attendance_pool.browse(cr, uid, matched_schedule_ids[0])
237+ if attendance_stop_hour <= calendar_attendance.hour_to and \
238+ (attendance_stop_hour - (calendar_attendance.hour_to -
239+ calendar_attendance.tolerance_from)) > -0.01: # handling float roundings (>=)
240+ additional_intervals = round(
241+ (calendar_attendance.hour_to - attendance_stop_hour) / precision)
242+ intervals_within += additional_intervals
243+ res[attendance.id]['duration'] = self.time_sum(
244+ res[attendance.id]['duration'], additional_intervals * precision)
245+
246+ res[attendance.id]['inside_calendar_duration'] = intervals_within * precision
247+ # make difference using time in order to avoid rounding errors
248+ # inside_calendar_duration can't be > duration
249+ res[attendance.id]['outside_calendar_duration'] = self.time_difference(
250+ res[attendance.id]['inside_calendar_duration'], res[attendance.id]['duration'], precision=precision)
251+
252+ if contract.working_hours.overtime_rounding:
253+ if res[attendance.id]['outside_calendar_duration']:
254+ overtime = res[attendance.id]['outside_calendar_duration']
255+ if contract.working_hours.overtime_rounding_tolerance:
256+ overtime = self.time_sum(overtime,
257+ contract.working_hours.overtime_rounding_tolerance)
258+ float_overtime_rounding = float(contract.working_hours.overtime_rounding)
259+ res[attendance.id]['outside_calendar_duration'] = math.floor(
260+ overtime * float_overtime_rounding) / float_overtime_rounding
261 return res
262
263 def _get_by_contracts(self, cr, uid, ids, context=None):
264@@ -338,7 +341,9 @@
265 elif attendance.action == 'sign_out':
266 previous_attendance_ids = self.search(cr, uid, [
267 ('employee_id', '=', attendance.employee_id.id),
268- ('name', '<', attendance.name)], order='name')
269+ ('name', '<', attendance.name),
270+ ('action', '=', 'sign_in'),
271+ ], order='name')
272 if previous_attendance_ids and previous_attendance_ids[len(previous_attendance_ids) - 1] not in attendance_ids:
273 attendance_ids.append(previous_attendance_ids[len(previous_attendance_ids) - 1])
274 return attendance_ids

Subscribers

People subscribed via source and target branches

to status/vote changes: