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