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
=== modified file 'hr_utilization/images/screenshot2.png'
0Binary 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 differ0Binary 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
=== modified file 'hr_utilization/report/hr_utilization_report.mako'
--- hr_utilization/report/hr_utilization_report.mako 2013-02-07 09:57:56 +0000
+++ hr_utilization/report/hr_utilization_report.mako 2013-06-11 12:25:39 +0000
@@ -61,10 +61,21 @@
61 /* border-right: 1px solid lightGrey; uncomment to active column lines */61 /* border-right: 1px solid lightGrey; uncomment to active column lines */
62 }62 }
63 .list_table .act_as_cell.first_column {63 .list_table .act_as_cell.first_column {
64 padding-left: 20px;
65 /* font-weight: bold; */
66 /* border-left: 1px solid lightGrey; uncomment to active column lines */
67 }
68
69 .list_table .act_as_cell.company_column {
64 padding-left: 0px;70 padding-left: 0px;
65 font-weight: bold;71 font-weight: bold;
66 /* border-left: 1px solid lightGrey; uncomment to active column lines */72 /* border-left: 1px solid lightGrey; uncomment to active column lines */
67 }73 }
74 .list_table .act_as_cell.department_column {
75 padding-left: 10px;
76 font-weight: bold;font-style:italic;
77 /* border-left: 1px solid lightGrey; uncomment to active column lines */
78 }
6879
69 .overflow_ellipsis {80 .overflow_ellipsis {
70 text-overflow: ellipsis;81 text-overflow: ellipsis;
@@ -76,8 +87,14 @@
76 <body>87 <body>
77 <%88 <%
78 setLang(user.lang) 89 setLang(user.lang)
79 lines = [line for line in data['res'].values() if 'pct' in line]90
80 lines_nc = [line for line in data['res'].values() if 'pct' not in line]91 lines = {id: line for (id, has_schedule), line in data['res'].items() if 'pct' in line}
92 departments = {id: line for (id, has_schedule), line in data['res_department'].items() if has_schedule}
93 companies = [line for (id, has_schedule), line in data['res_company'].items() if has_schedule]
94 lines_nc = {id: line for (id, has_schedule), line in data['res'].items() if 'pct' not in line}
95 departments_nc = {id: line for (id, has_schedule), line in data['res_department'].items() if not has_schedule}
96 companies_nc = [line for (id, has_schedule), line in data['res_company'].items() if not has_schedule]
97
81 column_names = data['column_names']98 column_names = data['column_names']
82 nb_cols=len(column_names)+299 nb_cols=len(column_names)+2
83 w1=100.0/(nb_cols+int(data['with_fte']))100 w1=100.0/(nb_cols+int(data['with_fte']))
@@ -106,17 +123,56 @@
106 %endif123 %endif
107 </div></div>124 </div></div>
108 <div class="act_as_tbody">125 <div class="act_as_tbody">
109 <!-- all lines sorted by sort criteria, then total line -->126 <!-- total line, then all lines sorted by sort criteria and group by company and department -->
110 %for u in sorted(lines, key=lambda u: -u['pct'][sort]) + [data['res_total']]:127 %for u in [data['res_total']]:
111 <div class="act_as_row lines">128 <div class="act_as_row lines">
112 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>129 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>
113 % for column_name in column_names:130 % for column_name in column_names:
114 <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div>131 <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div>
115 %endfor132 %endfor
116 %if data['with_fte']:133 %if data['with_fte']:
117 <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div>134 <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div>
118 %endif135 %endif
119 </div>136 </div>
137 %endfor
138 %for company in companies:
139 %if company['name']:
140 <div class="act_as_row lines">
141 <div class="act_as_cell company_column overflow_ellipsis">${company['name']}</div>
142 % for column_name in column_names:
143 <div class="act_as_cell amount" style="width: ${w1}%">${hrs(company['hours'][column_name])}<br/>${pct(company['pct'][column_name])}</div>
144 %endfor
145 %if data['with_fte']:
146 <div class="act_as_cell amount" style="width: ${w1}%">${company['fte']}</div>
147 %endif
148 </div>
149 %endif
150 %for department_id, department in departments.items():
151 %if department['name'] and department_id in company['departments']:
152 <div class="act_as_row lines">
153 <div class="act_as_cell department_column overflow_ellipsis">${department['name']}</div>
154 % for column_name in column_names:
155 <div class="act_as_cell amount" style="width: ${w1}%">${hrs(department['hours'][column_name])}<br/>${pct(department['pct'][column_name])}</div>
156 %endfor
157 %if data['with_fte']:
158 <div class="act_as_cell amount" style="width: ${w1}%">${company['fte']}</div>
159 %endif
160 </div>
161 %endif
162 %for user_id, u in sorted(lines.items(), key=lambda u: -u[1]['pct'][sort]):
163 %if user_id in department['users'] and user_id in company['users']:
164 <div class="act_as_row lines">
165 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>
166 % for column_name in column_names:
167 <div class="act_as_cell amount" style="width: ${w1}%">${hrs(u['hours'][column_name])}<br/>${pct(u['pct'][column_name])}</div>
168 %endfor
169 %if data['with_fte']:
170 <div class="act_as_cell amount" style="width: ${w1}%">${u['fte']}</div>
171 %endif
172 </div>
173 %endif
174 %endfor
175 %endfor
120 %endfor176 %endfor
121 </div>177 </div>
122 </div>178 </div>
@@ -132,15 +188,45 @@
132 <div class="act_as_cell amount" style="width: ${w2}%">${column_name}</div>188 <div class="act_as_cell amount" style="width: ${w2}%">${column_name}</div>
133 %endfor189 %endfor
134 </div></div>190 </div></div>
135 <div class="act_as_tbody">191 <div class="act_as_tbody">
136 %for u in sorted(lines_nc, key=lambda u: -u['hours'][sort]) + [data['res_nc_total']]:192 %for u in [data['res_nc_total']]:
137 <div class="act_as_row lines">193 <div class="act_as_row lines">
138 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>194 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>
139 % for column_name in column_names:195 % for column_name in column_names:
140 <div class="act_as_cell amount" style="width: ${w2}%">${hrs(u['hours'][column_name])}</div>196 <div class="act_as_cell amount" style="width: ${w2}%">${hrs(u['hours'][column_name])}</div>
141 %endfor197 %endfor
142 </div>198 </div>
199 %endfor
200 %for company in companies_nc:
201 %if company['name']:
202 <div class="act_as_row lines">
203 <div class="act_as_cell company_column overflow_ellipsis">${company['name']}</div>
204 % for column_name in column_names:
205 <div class="act_as_cell amount" style="width: ${w2}%">${hrs(company['hours'][column_name])}</div>
206 %endfor
207 </div>
208 %endif
209 %for department_id, department in departments_nc.items():
210 %if department['name'] and department_id in company['departments']:
211 <div class="act_as_row lines">
212 <div class="act_as_cell department_column overflow_ellipsis">${department['name']}</div>
213 % for column_name in column_names:
214 <div class="act_as_cell amount" style="width: ${w2}%">${hrs(company['hours'][column_name])}</div>
215 %endfor
216 </div>
217 %endif
218 %for user_id, u in sorted(lines_nc.items(), key=lambda u: -u[1]['hours'][sort]):
219 %if user_id in department['users'] and user_id in company['users']:
220 <div class="act_as_row lines">
221 <div class="act_as_cell first_column overflow_ellipsis">${u['name']}</div>
222 % for column_name in column_names:
223 <div class="act_as_cell amount" style="width: ${w2}%">${hrs(u['hours'][column_name])}</div>
224 %endfor
225 </div>
226 %endif
227 %endfor
143 %endfor228 %endfor
229 %endfor
144 </div>230 </div>
145 </div>231 </div>
146 %endif232 %endif
147233
=== modified file 'hr_utilization/report/hr_utilization_report.py'
--- hr_utilization/report/hr_utilization_report.py 2013-04-10 11:44:07 +0000
+++ hr_utilization/report/hr_utilization_report.py 2013-06-11 12:25:39 +0000
@@ -152,7 +152,7 @@
152 # (which is the OpenErp default and the convention used in account_analytic_analysis) 152 # (which is the OpenErp default and the convention used in account_analytic_analysis)
153 # XXX: this query assumes all timesheets are entered in hours153 # XXX: this query assumes all timesheets are entered in hours
154 self.cr.execute("""154 self.cr.execute("""
155 select al.user_id, al.account_id, r.name, r.company_id, c.id, sum(al.unit_amount)155 select e.department_id, al.user_id, al.account_id, r.name, r.company_id, c.id, sum(al.unit_amount)
156 from account_analytic_line al156 from account_analytic_line al
157 left join res_users u on u.id = al.user_id 157 left join res_users u on u.id = al.user_id
158 left join resource_resource r on r.user_id = u.id158 left join resource_resource r on r.user_id = u.id
@@ -163,15 +163,15 @@
163 (c.date_end is null or al.date <= c.date_end)163 (c.date_end is null or al.date <= c.date_end)
164 where al.journal_id = (select id from account_analytic_journal where type='general')164 where al.journal_id = (select id from account_analytic_journal where type='general')
165 and al.date >= %s and al.date <= %s165 and al.date >= %s and al.date <= %s
166 group by al.user_id, al.account_id, r.name, r.company_id, c.id166 group by e.department_id, al.user_id, al.account_id, r.name, r.company_id, c.id
167 order by r.name""", (data['period_start'], data['period_end']))167 order by r.name""", (data['period_start'], data['period_end']))
168168
169 res = {} # user_id: {'name':name,'columns':{column_name:hours}}169 res = {} # (user_id, has_schedule): {'name':name,'columns':{column_name:hours}}
170 for user_id, account_id, user_name, company_id, contract_id, hours in self.cr.fetchall():170 res_company = {} # (company_id, has_schedule): {'name':name, users:[user_ids], departments: [departmen_ids],}
171 if contract_id in contracts_with_schedule_by_id:171 res_department = {} # (department_id, has_schedule): {'name':name, users:[user_ids]}
172 key = (user_id, True)172 for department_id, user_id, account_id, user_name, company_id, contract_id, hours in self.cr.fetchall():
173 else:173 has_schedule = contract_id in contracts_with_schedule_by_id
174 key = (user_id, False)174 key = (user_id, has_schedule)
175 if key not in res:175 if key not in res:
176 res[key] = {176 res[key] = {
177 'name': user_name,177 'name': user_name,
@@ -179,14 +179,47 @@
179 'hours': {column_name:0.0 for column_name in column_names},179 'hours': {column_name:0.0 for column_name in column_names},
180 'contracts': {}, # contract_id: contract180 'contracts': {}, # contract_id: contract
181 }181 }
182 if only_total: 182 if only_total:
183 column_name = TOTAL183 column_name = TOTAL
184 else:184 else:
185 column_name = account_id_column_name_map.get(account_id, OTHER)185 column_name = account_id_column_name_map.get(account_id, OTHER)
186 res[key]['hours'][column_name] += hours186 res[key]['hours'][column_name] += hours
187 if contract_id in contracts_with_schedule_by_id:187 if has_schedule:
188 res[key]['contracts'][contract_id] = contracts_with_schedule_by_id[contract_id]188 res[key]['contracts'][contract_id] = contracts_with_schedule_by_id[contract_id]
189 189
190 if not data['group_by_company']:
191 company_id = (None, has_schedule)
192 if not data['group_by_department']:
193 department_id = (None, has_schedule)
194 if (company_id, has_schedule) not in res_company:
195 company_name = ''
196 if company_id:
197 company_name = self.pool.get("res.company").browse(self.cr, self.uid, company_id).name
198 res_company[(company_id, has_schedule)] = {
199 'name': company_name,
200 'users': [user_id],
201 'departments': [department_id],
202 'hours': {column_name:0.0 for column_name in column_names},}
203 else:
204 if user_id not in res_company[(company_id, has_schedule)]['users']:
205 res_company[(company_id, has_schedule)]['users'].append(user_id)
206 if department_id not in res_company[(company_id, has_schedule)]['departments']:
207 res_company[(company_id, has_schedule)]['departments'].append(department_id)
208
209 if (department_id, has_schedule) not in res_department:
210 department_name = ''
211 if department_id:
212 department_name = self.pool.get("hr.department").browse(self.cr, self.uid, department_id).name
213 res_department[(department_id, has_schedule)] = {
214 'name': department_name,
215 'users': [user_id],
216 'hours': {column_name:0.0 for column_name in column_names},}
217 else:
218 if user_id not in res_department[(department_id, has_schedule)]['users']:
219 res_department[(department_id, has_schedule)]['users'].append(user_id)
220 res_department[(department_id, has_schedule)]['hours'][column_name] += hours
221 res_company[(company_id, has_schedule)]['hours'][column_name] += hours
222
190 # initialize totals223 # initialize totals
191 users_without_contract = []224 users_without_contract = []
192 with_fte = configuration.with_fte225 with_fte = configuration.with_fte
@@ -205,35 +238,65 @@
205 }238 }
206239
207 # row total, percentages and fte for each row240 # row total, percentages and fte for each row
208 for (user_id, has_schedule), u in res.items():241 for (company_id, has_company_schedule), company in res_company.items():
209 # row total242 company_available_hours = 0.0
243 if with_fte:
244 company['fte'] = 0.0
210 if not only_total:245 if not only_total:
211 u['hours'][TOTAL] = reduce(lambda x,y: x+y, u['hours'].values())246 company['hours'][TOTAL] = reduce(lambda x,y: x+y, company['hours'].values())
247 for (department_id, has_department_schedule), department in res_department.items():
248 if department_id in company['departments']:
249 department_available_hours = 0.0
250 if with_fte:
251 department['fte'] = 0.0
252 if not only_total:
253 department['hours'][TOTAL] = reduce(lambda x,y: x+y, department['hours'].values())
254 for (user_id, has_schedule), u in res.items():
255 if user_id in department['users'] and user_id in company['users']:
256 # row total
257 if not only_total:
258 u['hours'][TOTAL] = reduce(lambda x,y: x+y, u['hours'].values())
212259
213 if has_schedule:260 if has_schedule:
214 # column totals261 # column totals
215 for column_name in column_names:262 for column_name in column_names:
216 res_total['hours'][column_name] += u['hours'][column_name]263 res_total['hours'][column_name] += u['hours'][column_name]
217 # percentage264 # percentage
218 available_hours = self.get_total_planned_working_hours(data['period_start'], data['period_end'], u['contracts'].values())265 available_hours = self.get_total_planned_working_hours(data['period_start'], data['period_end'], u['contracts'].values())
219 total_available_hours += available_hours266 total_available_hours += available_hours
220 u['pct'] = { column_name: hours/available_hours for column_name, hours in u['hours'].items() }267 company_available_hours += available_hours
221 # fte268 department_available_hours += available_hours
222 if with_fte:269 u['pct'] = { column_name: hours/available_hours for column_name, hours in u['hours'].items() }
223 company = company_obj.browse(self.cr, self.uid, [u['company_id']])[0]270 # fte
224 if company.fulltime_calendar_id:271 if with_fte:
225 fte_available_hours = self.get_planned_working_hours(company.fulltime_calendar_id, data['period_start'], data['period_end'])272 company_u = company_obj.browse(self.cr, self.uid, [u['company_id']])[0]
226 fte = available_hours / fte_available_hours273 if company_u.fulltime_calendar_id:
227 res_total['fte'] += fte274 fte_available_hours = self.get_planned_working_hours(company_u.fulltime_calendar_id, data['period_start'], data['period_end'])
228 u['fte'] = "%.1f" % fte275 fte = available_hours / fte_available_hours
229 else:276 res_total['fte'] += fte
230 u['fte'] = NA277 company['fte'] += fte
231 fte_with_na = True278 department['fte'] += fte
232 else:279 u['fte'] = "%.1f" % fte
233 users_without_contract.append(u['name'])280 else:
234 # column totals281 u['fte'] = NA
235 for column_name in column_names:282 fte_with_na = True
236 res_nc_total['hours'][column_name] += u['hours'][column_name]283 else:
284 users_without_contract.append(u['name'])
285 # column totals
286 for column_name in column_names:
287 res_nc_total['hours'][column_name] += u['hours'][column_name]
288 if has_company_schedule and has_department_schedule:
289 department['pct'] = { column_name: hours/department_available_hours for column_name, hours in department['hours'].items() }
290 if with_fte and fte_with_na and not(department['fte']):
291 department['fte'] = NA
292 else:
293 department['fte'] = "%.1f" % department['fte']
294 if has_company_schedule:
295 company['pct'] = { column_name: hours/company_available_hours for column_name, hours in company['hours'].items() }
296 if with_fte and fte_with_na and not(company['fte']):
297 company['fte'] = NA
298 else:
299 company['fte'] = "%.1f" % company['fte']
237300
238 # total average percentage301 # total average percentage
239 if total_available_hours:302 if total_available_hours:
@@ -249,6 +312,8 @@
249312
250 # set data in context for report313 # set data in context for report
251 data['res'] = res314 data['res'] = res
315 data['res_department'] = res_department
316 data['res_company'] = res_company
252 data['res_total'] = res_total317 data['res_total'] = res_total
253 data['res_nc_total'] = res_nc_total318 data['res_nc_total'] = res_nc_total
254 data['users_without_contract'] = users_without_contract319 data['users_without_contract'] = users_without_contract
@@ -264,5 +329,5 @@
264 'hr.utilization.print',329 'hr.utilization.print',
265 rml='addons/hr_utilization/report/hr_utilization_report.mako',330 rml='addons/hr_utilization/report/hr_utilization_report.mako',
266 parser=hr_utilization_report)331 parser=hr_utilization_report)
267332
268# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:333# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
269334
=== modified file 'hr_utilization/wizard/hr_utilization_print.py'
--- hr_utilization/wizard/hr_utilization_print.py 2013-02-07 09:57:56 +0000
+++ hr_utilization/wizard/hr_utilization_print.py 2013-06-11 12:25:39 +0000
@@ -39,6 +39,8 @@
39 'configuration_id': fields.many2one('hr.utilization.configuration','Configuration', required=True),39 'configuration_id': fields.many2one('hr.utilization.configuration','Configuration', required=True),
40 'period_start': fields.date("Period start", required=True),40 'period_start': fields.date("Period start", required=True),
41 'period_end': fields.date("Period end", required=True),41 'period_end': fields.date("Period end", required=True),
42 'group_by_company': fields.boolean('Group by company'),
43 'group_by_department': fields.boolean('Group by department'),
42 }44 }
4345
44 def default_get(self, cr, uid, fields, context=None):46 def default_get(self, cr, uid, fields, context=None):
@@ -60,7 +62,7 @@
60 62
61 def print_report(self, cr, uid, ids, context=None):63 def print_report(self, cr, uid, ids, context=None):
62 assert len(ids) == 164 assert len(ids) == 1
63 data = self.read(cr,uid,ids,["configuration_id","period_start","period_end"],context)[0]65 data = self.read(cr,uid,ids,["configuration_id","period_start","period_end","group_by_department","group_by_company"],context)[0]
64 return {'type': 'ir.actions.report.xml',66 return {'type': 'ir.actions.report.xml',
65 'report_name': 'hr.utilization.report',67 'report_name': 'hr.utilization.report',
66 'datas': data}68 'datas': data}
6769
=== modified file 'hr_utilization/wizard/hr_utilization_print.xml'
--- hr_utilization/wizard/hr_utilization_print.xml 2013-02-07 09:57:56 +0000
+++ hr_utilization/wizard/hr_utilization_print.xml 2013-06-11 12:25:39 +0000
@@ -11,6 +11,8 @@
11 <field name="configuration_id" colspan="3"/>11 <field name="configuration_id" colspan="3"/>
12 <field name="period_start"/>12 <field name="period_start"/>
13 <field name="period_end"/>13 <field name="period_end"/>
14 <field name="group_by_company"/>
15 <field name="group_by_department"/>
14 </group>16 </group>
15 <footer>17 <footer>
16 <button string="Print" name="print_report" type="object" class="oe_highlight"/>18 <button string="Print" name="print_report" type="object" class="oe_highlight"/>

Subscribers

People subscribed via source and target branches