Merge lp:~laetitia-gangloff/acsone-addons/hr_utilization_group_by_report into lp:~acsone-openerp/acsone-addons/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
Reviewer Review Type Date Requested Status
Acsone OpenErp Team Pending
Review via email: mp+168651@code.launchpad.net

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'
2Binary 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"/>

Subscribers

People subscribed via source and target branches