Merge lp:~laetitia-gangloff/acsone-addons/hr_utilization_group_by_report into lp:~acsone-openerp/acsone-addons/7.0
- hr_utilization_group_by_report
- Merge into 7.0
Proposed by
Laetitia Gangloff (Acsone)
Status: | Needs review |
---|---|
Proposed branch: | lp:~laetitia-gangloff/acsone-addons/hr_utilization_group_by_report |
Merge into: | lp:~acsone-openerp/acsone-addons/7.0 |
Diff against target: |
392 lines (+211/-56) 4 files modified
hr_utilization/report/hr_utilization_report.mako (+101/-15) hr_utilization/report/hr_utilization_report.py (+105/-40) hr_utilization/wizard/hr_utilization_print.py (+3/-1) hr_utilization/wizard/hr_utilization_print.xml (+2/-0) |
To merge this branch: | bzr merge lp:~laetitia-gangloff/acsone-addons/hr_utilization_group_by_report |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Acsone OpenErp Team | Pending | ||
Review via email: mp+168651@code.launchpad.net |
Commit message
Description of the change
This change add group by company/department function for the report generation.
To post a comment you must log in.
Unmerged revisions
- 36. By Laetitia Gangloff (Acsone)
-
hr_utilization: add group by department/company for the report
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hr_utilization/images/screenshot2.png' |
2 | Binary files hr_utilization/images/screenshot2.png 2013-02-07 09:57:56 +0000 and hr_utilization/images/screenshot2.png 2013-06-11 12:25:39 +0000 differ |
3 | === modified file 'hr_utilization/report/hr_utilization_report.mako' |
4 | --- hr_utilization/report/hr_utilization_report.mako 2013-02-07 09:57:56 +0000 |
5 | +++ hr_utilization/report/hr_utilization_report.mako 2013-06-11 12:25:39 +0000 |
6 | @@ -61,10 +61,21 @@ |
7 | /* border-right: 1px solid lightGrey; uncomment to active column lines */ |
8 | } |
9 | .list_table .act_as_cell.first_column { |
10 | + padding-left: 20px; |
11 | + /* font-weight: bold; */ |
12 | + /* border-left: 1px solid lightGrey; uncomment to active column lines */ |
13 | + } |
14 | + |
15 | + .list_table .act_as_cell.company_column { |
16 | padding-left: 0px; |
17 | font-weight: bold; |
18 | /* border-left: 1px solid lightGrey; uncomment to active column lines */ |
19 | } |
20 | + .list_table .act_as_cell.department_column { |
21 | + padding-left: 10px; |
22 | + font-weight: bold;font-style:italic; |
23 | + /* border-left: 1px solid lightGrey; uncomment to active column lines */ |
24 | + } |
25 | |
26 | .overflow_ellipsis { |
27 | text-overflow: ellipsis; |
28 | @@ -76,8 +87,14 @@ |
29 | <body> |
30 | <% |
31 | setLang(user.lang) |
32 | - lines = [line for line in data['res'].values() if 'pct' in line] |
33 | - lines_nc = [line for line in data['res'].values() if 'pct' not in line] |
34 | + |
35 | + lines = {id: line for (id, has_schedule), line in data['res'].items() if 'pct' in line} |
36 | + departments = {id: line for (id, has_schedule), line in data['res_department'].items() if has_schedule} |
37 | + companies = [line for (id, has_schedule), line in data['res_company'].items() if has_schedule] |
38 | + lines_nc = {id: line for (id, has_schedule), line in data['res'].items() if 'pct' not in line} |
39 | + departments_nc = {id: line for (id, has_schedule), line in data['res_department'].items() if not has_schedule} |
40 | + companies_nc = [line for (id, has_schedule), line in data['res_company'].items() if not has_schedule] |
41 | + |
42 | column_names = data['column_names'] |
43 | nb_cols=len(column_names)+2 |
44 | w1=100.0/(nb_cols+int(data['with_fte'])) |
45 | @@ -106,17 +123,56 @@ |
46 | %endif |
47 | </div></div> |
48 | <div class="act_as_tbody"> |
49 | - <!-- all lines sorted by sort criteria, then total line --> |
50 | - %for u in sorted(lines, key=lambda u: -u['pct'][sort]) + [data['res_total']]: |
51 | - <div class="act_as_row lines"> |
52 | - <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div> |
53 | - % for column_name in column_names: |
54 | - <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div> |
55 | - %endfor |
56 | - %if data['with_fte']: |
57 | - <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div> |
58 | - %endif |
59 | - </div> |
60 | + <!-- total line, then all lines sorted by sort criteria and group by company and department --> |
61 | + %for u in [data['res_total']]: |
62 | + <div class="act_as_row lines"> |
63 | + <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div> |
64 | + % for column_name in column_names: |
65 | + <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div> |
66 | + %endfor |
67 | + %if data['with_fte']: |
68 | + <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div> |
69 | + %endif |
70 | + </div> |
71 | + %endfor |
72 | + %for company in companies: |
73 | + %if company['name']: |
74 | + <div class="act_as_row lines"> |
75 | + <div class="act_as_cell company_column overflow_ellipsis">${company['name']}</div> |
76 | + % for column_name in column_names: |
77 | + <div class="act_as_cell amount" style="width: ${w1}%">${hrs(company['hours'][column_name])}<br/>${pct(company['pct'][column_name])}</div> |
78 | + %endfor |
79 | + %if data['with_fte']: |
80 | + <div class="act_as_cell amount" style="width: ${w1}%">${company['fte']}</div> |
81 | + %endif |
82 | + </div> |
83 | + %endif |
84 | + %for department_id, department in departments.items(): |
85 | + %if department['name'] and department_id in company['departments']: |
86 | + <div class="act_as_row lines"> |
87 | + <div class="act_as_cell department_column overflow_ellipsis">${department['name']}</div> |
88 | + % for column_name in column_names: |
89 | + <div class="act_as_cell amount" style="width: ${w1}%">${hrs(department['hours'][column_name])}<br/>${pct(department['pct'][column_name])}</div> |
90 | + %endfor |
91 | + %if data['with_fte']: |
92 | + <div class="act_as_cell amount" style="width: ${w1}%">${company['fte']}</div> |
93 | + %endif |
94 | + </div> |
95 | + %endif |
96 | + %for user_id, u in sorted(lines.items(), key=lambda u: -u[1]['pct'][sort]): |
97 | + %if user_id in department['users'] and user_id in company['users']: |
98 | + <div class="act_as_row lines"> |
99 | + <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div> |
100 | + % for column_name in column_names: |
101 | + <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div> |
102 | + %endfor |
103 | + %if data['with_fte']: |
104 | + <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div> |
105 | + %endif |
106 | + </div> |
107 | + %endif |
108 | + %endfor |
109 | + %endfor |
110 | %endfor |
111 | </div> |
112 | </div> |
113 | @@ -132,15 +188,45 @@ |
114 | <div class="act_as_cell amount" style="width: ${w2}%">${column_name}</div> |
115 | %endfor |
116 | </div></div> |
117 | - <div class="act_as_tbody"> |
118 | - %for u in sorted(lines_nc, key=lambda u: -u['hours'][sort]) + [data['res_nc_total']]: |
119 | + <div class="act_as_tbody"> |
120 | + %for u in [data['res_nc_total']]: |
121 | <div class="act_as_row lines"> |
122 | <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div> |
123 | % for column_name in column_names: |
124 | <div class="act_as_cell amount" style="width: ${w2}%">${hrs(u['hours'][column_name])}</div> |
125 | %endfor |
126 | </div> |
127 | + %endfor |
128 | + %for company in companies_nc: |
129 | + %if company['name']: |
130 | + <div class="act_as_row lines"> |
131 | + <div class="act_as_cell company_column overflow_ellipsis">${company['name']}</div> |
132 | + % for column_name in column_names: |
133 | + <div class="act_as_cell amount" style="width: ${w2}%">${hrs(company['hours'][column_name])}</div> |
134 | + %endfor |
135 | + </div> |
136 | + %endif |
137 | + %for department_id, department in departments_nc.items(): |
138 | + %if department['name'] and department_id in company['departments']: |
139 | + <div class="act_as_row lines"> |
140 | + <div class="act_as_cell department_column overflow_ellipsis">${department['name']}</div> |
141 | + % for column_name in column_names: |
142 | + <div class="act_as_cell amount" style="width: ${w2}%">${hrs(company['hours'][column_name])}</div> |
143 | + %endfor |
144 | + </div> |
145 | + %endif |
146 | + %for user_id, u in sorted(lines_nc.items(), key=lambda u: -u[1]['hours'][sort]): |
147 | + %if user_id in department['users'] and user_id in company['users']: |
148 | + <div class="act_as_row lines"> |
149 | + <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div> |
150 | + % for column_name in column_names: |
151 | + <div class="act_as_cell amount" style="width: ${w2}%">${hrs(u['hours'][column_name])}</div> |
152 | + %endfor |
153 | + </div> |
154 | + %endif |
155 | + %endfor |
156 | %endfor |
157 | + %endfor |
158 | </div> |
159 | </div> |
160 | %endif |
161 | |
162 | === modified file 'hr_utilization/report/hr_utilization_report.py' |
163 | --- hr_utilization/report/hr_utilization_report.py 2013-04-10 11:44:07 +0000 |
164 | +++ hr_utilization/report/hr_utilization_report.py 2013-06-11 12:25:39 +0000 |
165 | @@ -152,7 +152,7 @@ |
166 | # (which is the OpenErp default and the convention used in account_analytic_analysis) |
167 | # XXX: this query assumes all timesheets are entered in hours |
168 | self.cr.execute(""" |
169 | - select al.user_id, al.account_id, r.name, r.company_id, c.id, sum(al.unit_amount) |
170 | + select e.department_id, al.user_id, al.account_id, r.name, r.company_id, c.id, sum(al.unit_amount) |
171 | from account_analytic_line al |
172 | left join res_users u on u.id = al.user_id |
173 | left join resource_resource r on r.user_id = u.id |
174 | @@ -163,15 +163,15 @@ |
175 | (c.date_end is null or al.date <= c.date_end) |
176 | where al.journal_id = (select id from account_analytic_journal where type='general') |
177 | and al.date >= %s and al.date <= %s |
178 | - group by al.user_id, al.account_id, r.name, r.company_id, c.id |
179 | + group by e.department_id, al.user_id, al.account_id, r.name, r.company_id, c.id |
180 | order by r.name""", (data['period_start'], data['period_end'])) |
181 | |
182 | - res = {} # user_id: {'name':name,'columns':{column_name:hours}} |
183 | - for user_id, account_id, user_name, company_id, contract_id, hours in self.cr.fetchall(): |
184 | - if contract_id in contracts_with_schedule_by_id: |
185 | - key = (user_id, True) |
186 | - else: |
187 | - key = (user_id, False) |
188 | + res = {} # (user_id, has_schedule): {'name':name,'columns':{column_name:hours}} |
189 | + res_company = {} # (company_id, has_schedule): {'name':name, users:[user_ids], departments: [departmen_ids],} |
190 | + res_department = {} # (department_id, has_schedule): {'name':name, users:[user_ids]} |
191 | + for department_id, user_id, account_id, user_name, company_id, contract_id, hours in self.cr.fetchall(): |
192 | + has_schedule = contract_id in contracts_with_schedule_by_id |
193 | + key = (user_id, has_schedule) |
194 | if key not in res: |
195 | res[key] = { |
196 | 'name': user_name, |
197 | @@ -179,14 +179,47 @@ |
198 | 'hours': {column_name:0.0 for column_name in column_names}, |
199 | 'contracts': {}, # contract_id: contract |
200 | } |
201 | - if only_total: |
202 | + if only_total: |
203 | column_name = TOTAL |
204 | else: |
205 | column_name = account_id_column_name_map.get(account_id, OTHER) |
206 | res[key]['hours'][column_name] += hours |
207 | - if contract_id in contracts_with_schedule_by_id: |
208 | + if has_schedule: |
209 | res[key]['contracts'][contract_id] = contracts_with_schedule_by_id[contract_id] |
210 | - |
211 | + |
212 | + if not data['group_by_company']: |
213 | + company_id = (None, has_schedule) |
214 | + if not data['group_by_department']: |
215 | + department_id = (None, has_schedule) |
216 | + if (company_id, has_schedule) not in res_company: |
217 | + company_name = '' |
218 | + if company_id: |
219 | + company_name = self.pool.get("res.company").browse(self.cr, self.uid, company_id).name |
220 | + res_company[(company_id, has_schedule)] = { |
221 | + 'name': company_name, |
222 | + 'users': [user_id], |
223 | + 'departments': [department_id], |
224 | + 'hours': {column_name:0.0 for column_name in column_names},} |
225 | + else: |
226 | + if user_id not in res_company[(company_id, has_schedule)]['users']: |
227 | + res_company[(company_id, has_schedule)]['users'].append(user_id) |
228 | + if department_id not in res_company[(company_id, has_schedule)]['departments']: |
229 | + res_company[(company_id, has_schedule)]['departments'].append(department_id) |
230 | + |
231 | + if (department_id, has_schedule) not in res_department: |
232 | + department_name = '' |
233 | + if department_id: |
234 | + department_name = self.pool.get("hr.department").browse(self.cr, self.uid, department_id).name |
235 | + res_department[(department_id, has_schedule)] = { |
236 | + 'name': department_name, |
237 | + 'users': [user_id], |
238 | + 'hours': {column_name:0.0 for column_name in column_names},} |
239 | + else: |
240 | + if user_id not in res_department[(department_id, has_schedule)]['users']: |
241 | + res_department[(department_id, has_schedule)]['users'].append(user_id) |
242 | + res_department[(department_id, has_schedule)]['hours'][column_name] += hours |
243 | + res_company[(company_id, has_schedule)]['hours'][column_name] += hours |
244 | + |
245 | # initialize totals |
246 | users_without_contract = [] |
247 | with_fte = configuration.with_fte |
248 | @@ -205,35 +238,65 @@ |
249 | } |
250 | |
251 | # row total, percentages and fte for each row |
252 | - for (user_id, has_schedule), u in res.items(): |
253 | - # row total |
254 | + for (company_id, has_company_schedule), company in res_company.items(): |
255 | + company_available_hours = 0.0 |
256 | + if with_fte: |
257 | + company['fte'] = 0.0 |
258 | if not only_total: |
259 | - u['hours'][TOTAL] = reduce(lambda x,y: x+y, u['hours'].values()) |
260 | + company['hours'][TOTAL] = reduce(lambda x,y: x+y, company['hours'].values()) |
261 | + for (department_id, has_department_schedule), department in res_department.items(): |
262 | + if department_id in company['departments']: |
263 | + department_available_hours = 0.0 |
264 | + if with_fte: |
265 | + department['fte'] = 0.0 |
266 | + if not only_total: |
267 | + department['hours'][TOTAL] = reduce(lambda x,y: x+y, department['hours'].values()) |
268 | + for (user_id, has_schedule), u in res.items(): |
269 | + if user_id in department['users'] and user_id in company['users']: |
270 | + # row total |
271 | + if not only_total: |
272 | + u['hours'][TOTAL] = reduce(lambda x,y: x+y, u['hours'].values()) |
273 | |
274 | - if has_schedule: |
275 | - # column totals |
276 | - for column_name in column_names: |
277 | - res_total['hours'][column_name] += u['hours'][column_name] |
278 | - # percentage |
279 | - available_hours = self.get_total_planned_working_hours(data['period_start'], data['period_end'], u['contracts'].values()) |
280 | - total_available_hours += available_hours |
281 | - u['pct'] = { column_name: hours/available_hours for column_name, hours in u['hours'].items() } |
282 | - # fte |
283 | - if with_fte: |
284 | - company = company_obj.browse(self.cr, self.uid, [u['company_id']])[0] |
285 | - if company.fulltime_calendar_id: |
286 | - fte_available_hours = self.get_planned_working_hours(company.fulltime_calendar_id, data['period_start'], data['period_end']) |
287 | - fte = available_hours / fte_available_hours |
288 | - res_total['fte'] += fte |
289 | - u['fte'] = "%.1f" % fte |
290 | - else: |
291 | - u['fte'] = NA |
292 | - fte_with_na = True |
293 | - else: |
294 | - users_without_contract.append(u['name']) |
295 | - # column totals |
296 | - for column_name in column_names: |
297 | - res_nc_total['hours'][column_name] += u['hours'][column_name] |
298 | + if has_schedule: |
299 | + # column totals |
300 | + for column_name in column_names: |
301 | + res_total['hours'][column_name] += u['hours'][column_name] |
302 | + # percentage |
303 | + available_hours = self.get_total_planned_working_hours(data['period_start'], data['period_end'], u['contracts'].values()) |
304 | + total_available_hours += available_hours |
305 | + company_available_hours += available_hours |
306 | + department_available_hours += available_hours |
307 | + u['pct'] = { column_name: hours/available_hours for column_name, hours in u['hours'].items() } |
308 | + # fte |
309 | + if with_fte: |
310 | + company_u = company_obj.browse(self.cr, self.uid, [u['company_id']])[0] |
311 | + if company_u.fulltime_calendar_id: |
312 | + fte_available_hours = self.get_planned_working_hours(company_u.fulltime_calendar_id, data['period_start'], data['period_end']) |
313 | + fte = available_hours / fte_available_hours |
314 | + res_total['fte'] += fte |
315 | + company['fte'] += fte |
316 | + department['fte'] += fte |
317 | + u['fte'] = "%.1f" % fte |
318 | + else: |
319 | + u['fte'] = NA |
320 | + fte_with_na = True |
321 | + else: |
322 | + users_without_contract.append(u['name']) |
323 | + # column totals |
324 | + for column_name in column_names: |
325 | + res_nc_total['hours'][column_name] += u['hours'][column_name] |
326 | + if has_company_schedule and has_department_schedule: |
327 | + department['pct'] = { column_name: hours/department_available_hours for column_name, hours in department['hours'].items() } |
328 | + if with_fte and fte_with_na and not(department['fte']): |
329 | + department['fte'] = NA |
330 | + else: |
331 | + department['fte'] = "%.1f" % department['fte'] |
332 | + if has_company_schedule: |
333 | + company['pct'] = { column_name: hours/company_available_hours for column_name, hours in company['hours'].items() } |
334 | + if with_fte and fte_with_na and not(company['fte']): |
335 | + company['fte'] = NA |
336 | + else: |
337 | + company['fte'] = "%.1f" % company['fte'] |
338 | |
339 | # total average percentage |
340 | if total_available_hours: |
341 | @@ -249,6 +312,8 @@ |
342 | |
343 | # set data in context for report |
344 | data['res'] = res |
345 | + data['res_department'] = res_department |
346 | + data['res_company'] = res_company |
347 | data['res_total'] = res_total |
348 | data['res_nc_total'] = res_nc_total |
349 | data['users_without_contract'] = users_without_contract |
350 | @@ -264,5 +329,5 @@ |
351 | 'hr.utilization.print', |
352 | rml='addons/hr_utilization/report/hr_utilization_report.mako', |
353 | parser=hr_utilization_report) |
354 | - |
355 | -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
356 | + |
357 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
358 | |
359 | === modified file 'hr_utilization/wizard/hr_utilization_print.py' |
360 | --- hr_utilization/wizard/hr_utilization_print.py 2013-02-07 09:57:56 +0000 |
361 | +++ hr_utilization/wizard/hr_utilization_print.py 2013-06-11 12:25:39 +0000 |
362 | @@ -39,6 +39,8 @@ |
363 | 'configuration_id': fields.many2one('hr.utilization.configuration','Configuration', required=True), |
364 | 'period_start': fields.date("Period start", required=True), |
365 | 'period_end': fields.date("Period end", required=True), |
366 | + 'group_by_company': fields.boolean('Group by company'), |
367 | + 'group_by_department': fields.boolean('Group by department'), |
368 | } |
369 | |
370 | def default_get(self, cr, uid, fields, context=None): |
371 | @@ -60,7 +62,7 @@ |
372 | |
373 | def print_report(self, cr, uid, ids, context=None): |
374 | assert len(ids) == 1 |
375 | - data = self.read(cr,uid,ids,["configuration_id","period_start","period_end"],context)[0] |
376 | + data = self.read(cr,uid,ids,["configuration_id","period_start","period_end","group_by_department","group_by_company"],context)[0] |
377 | return {'type': 'ir.actions.report.xml', |
378 | 'report_name': 'hr.utilization.report', |
379 | 'datas': data} |
380 | |
381 | === modified file 'hr_utilization/wizard/hr_utilization_print.xml' |
382 | --- hr_utilization/wizard/hr_utilization_print.xml 2013-02-07 09:57:56 +0000 |
383 | +++ hr_utilization/wizard/hr_utilization_print.xml 2013-06-11 12:25:39 +0000 |
384 | @@ -11,6 +11,8 @@ |
385 | <field name="configuration_id" colspan="3"/> |
386 | <field name="period_start"/> |
387 | <field name="period_end"/> |
388 | + <field name="group_by_company"/> |
389 | + <field name="group_by_department"/> |
390 | </group> |
391 | <footer> |
392 | <button string="Print" name="print_report" type="object" class="oe_highlight"/> |