Merge lp:~elbati/domsense-agilebg-addons/6.1_hr_attendance_analysis_ref into lp:domsense-agilebg-addons/6.1
- 6.1_hr_attendance_analysis_ref
- Merge into 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 |
Related bugs: |
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
Description of the change
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 |