Merge lp:~camptocamp/hr-timesheet/add-test-for-task-project-indicators-jge into lp:~hr-core-editors/hr-timesheet/7.0
- add-test-for-task-project-indicators-jge
- Merge into 7.0
Proposed by
Nicolas Bessi - Camptocamp
Status: | Merged |
---|---|
Merged at revision: | 56 |
Proposed branch: | lp:~camptocamp/hr-timesheet/add-test-for-task-project-indicators-jge |
Merge into: | lp:~hr-core-editors/hr-timesheet/7.0 |
Diff against target: |
568 lines (+370/-69) 3 files modified
timesheet_task/__openerp__.py (+16/-5) timesheet_task/project_task.py (+72/-64) timesheet_task/test/task_timesheet_indicators.yml (+282/-0) |
To merge this branch: | bzr merge lp:~camptocamp/hr-timesheet/add-test-for-task-project-indicators-jge |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guewen Baconnier @ Camptocamp | code review | Approve | |
Joël Grand-Guillaume @ camptocamp | code review + tests | Approve | |
Review via email: mp+186816@code.launchpad.net |
Commit message
[IMP] in timesheet_task module:
Fix propagation of indicator at project level when aa/timesheet line are altered
Fix remaining_hours when a task is added on aa line without task
Add YAML tests
Improve tooltips of indicators
Description of the change
[IMP] in timesheet_task module:
Fix propagation of indicator at project level when aa/timesheet line are altered
Fix remaining_hours when a task is added on aa line without task
Add YAML tests
Improve tooltips of indicators
Please feel free to correct english.
To post a comment you must log in.
- 69. By Nicolas Bessi - Camptocamp
-
[REVERT] wrongly merged revisions
- 70. By Nicolas Bessi - Camptocamp
-
[FIX] comment english
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
review:
Approve
(code review + tests)
- 71. By Guewen Baconnier @ Camptocamp
-
[IMP] description in manifest, some typos and layout
- 72. By Guewen Baconnier @ Camptocamp
-
[FIX] no mutable in args
- 73. By Guewen Baconnier @ Camptocamp
-
[FIX] alignment
Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
Pushed some nitpicker's changes.
LGTM, thanks
review:
Approve
(code review)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'timesheet_task/__openerp__.py' | |||
2 | --- timesheet_task/__openerp__.py 2013-04-03 11:20:21 +0000 | |||
3 | +++ timesheet_task/__openerp__.py 2013-10-08 11:06:42 +0000 | |||
4 | @@ -18,18 +18,29 @@ | |||
5 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
6 | 19 | # | 19 | # |
7 | 20 | ############################################################################## | 20 | ############################################################################## |
9 | 21 | {'name' : 'Analytic Task', | 21 | {'name' : 'Analytic Timesheet In Task', |
10 | 22 | 'version' : '0.2', | 22 | 'version' : '0.2', |
11 | 23 | 'author' : 'Camptocamp', | 23 | 'author' : 'Camptocamp', |
13 | 24 | 'maintainer': 'Camptocamp - Acsone SA/NV', | 24 | 'maintainer': 'Camptocamp, Acsone SA/NV', |
14 | 25 | 'category': 'Human Resources', | 25 | 'category': 'Human Resources', |
15 | 26 | 'depends' : ['project', 'hr_timesheet_invoice'], | 26 | 'depends' : ['project', 'hr_timesheet_invoice'], |
18 | 27 | 'description': """Replace project.task.work items linked to task | 27 | 'description': """ |
19 | 28 | with hr.analytic.timesheet""", | 28 | Replace task work items (project.task.work) linked to task with |
20 | 29 | timesheet lines (hr.analytic.timesheet). | ||
21 | 30 | |||
22 | 31 | Unless the module project_timesheet, it allows to have only one single | ||
23 | 32 | object that handles and records time spent by employees, making more | ||
24 | 33 | coherence for the end user. This way, time entered through timesheet | ||
25 | 34 | lines or tasks is the same. As long as a timesheet lines has an | ||
26 | 35 | associated task, it will compute the related indicators. | ||
27 | 36 | |||
28 | 37 | Used with the module hr_timesheet_task, it also allows users to complete | ||
29 | 38 | task information through the timesheet sheet (hr.timesheet.sheet). | ||
30 | 39 | """, | ||
31 | 29 | 'website': 'http://www.camptocamp.com', | 40 | 'website': 'http://www.camptocamp.com', |
32 | 30 | 'data': ['project_task_view.xml'], | 41 | 'data': ['project_task_view.xml'], |
33 | 31 | 'demo': [], | 42 | 'demo': [], |
35 | 32 | 'test': [], | 43 | 'test': ['test/task_timesheet_indicators.yml'], |
36 | 33 | 'installable': True, | 44 | 'installable': True, |
37 | 34 | 'images' : [], | 45 | 'images' : [], |
38 | 35 | 'auto_install': False, | 46 | 'auto_install': False, |
39 | 36 | 47 | ||
40 | === modified file 'timesheet_task/project_task.py' | |||
41 | --- timesheet_task/project_task.py 2013-08-14 15:05:12 +0000 | |||
42 | +++ timesheet_task/project_task.py 2013-10-08 11:06:42 +0000 | |||
43 | @@ -20,23 +20,23 @@ | |||
44 | 20 | ############################################################################## | 20 | ############################################################################## |
45 | 21 | 21 | ||
46 | 22 | from openerp.osv import orm, fields | 22 | from openerp.osv import orm, fields |
47 | 23 | from openerp import SUPERUSER_ID | ||
48 | 23 | 24 | ||
51 | 24 | TASK_WATCHERS = ['work_ids', 'remaining_hours', 'planned_hours'] | 25 | TASK_WATCHERS = ['work_ids', 'remaining_hours', 'effective_hours', 'planned_hours'] |
52 | 25 | TIMESHEET_WATCHERS = ['unit_amount', 'product_uom_id', 'account_id', 'to_invoice', 'task_id'] | 26 | TIMESHEET_WATCHERS = ['unit_amount', 'product_uom_id', 'account_id', 'task_id'] |
53 | 26 | 27 | ||
54 | 27 | class ProjectTask(orm.Model): | 28 | class ProjectTask(orm.Model): |
55 | 28 | _inherit = "project.task" | 29 | _inherit = "project.task" |
56 | 29 | _name = "project.task" | 30 | _name = "project.task" |
57 | 30 | 31 | ||
58 | 31 | |||
59 | 32 | def _progress_rate(self, cr, uid, ids, names, arg, context=None): | 32 | def _progress_rate(self, cr, uid, ids, names, arg, context=None): |
60 | 33 | """TODO improve code taken for OpenERP""" | 33 | """TODO improve code taken for OpenERP""" |
61 | 34 | res = {} | 34 | res = {} |
65 | 35 | cr.execute("""SELECT task_id, COALESCE(SUM(unit_amount),0) | 35 | cr.execute("""SELECT task_id, COALESCE(SUM(unit_amount),0) |
66 | 36 | FROM account_analytic_line | 36 | FROM account_analytic_line |
67 | 37 | WHERE task_id IN %s | 37 | WHERE task_id IN %s |
68 | 38 | GROUP BY task_id""", (tuple(ids),)) | 38 | GROUP BY task_id""", (tuple(ids),)) |
70 | 39 | 39 | ||
71 | 40 | hours = dict(cr.fetchall()) | 40 | hours = dict(cr.fetchall()) |
72 | 41 | for task in self.browse(cr, uid, ids, context=context): | 41 | for task in self.browse(cr, uid, ids, context=context): |
73 | 42 | res[task.id] = {} | 42 | res[task.id] = {} |
74 | @@ -51,47 +51,59 @@ | |||
75 | 51 | return res | 51 | return res |
76 | 52 | 52 | ||
77 | 53 | 53 | ||
78 | 54 | def _store_set_values(self, cr, uid, ids, fields, context=None): | ||
79 | 55 | # Hack to avoid redefining most of function fields of project.project model | ||
80 | 56 | # This is mainly due to the fact that orm _store_set_values use direct access to database. | ||
81 | 57 | # So when modifiy aa line the _store_set_values as it uses cursor directly to update tasks | ||
82 | 58 | # project triggers on task are not called | ||
83 | 59 | res = super(ProjectTask, self)._store_set_values(cr, uid, ids, fields, context=context) | ||
84 | 60 | for row in self.browse(cr, SUPERUSER_ID, ids, context=context): | ||
85 | 61 | project = row.project_id | ||
86 | 62 | project.write({'parent_id': project.parent_id.id}) | ||
87 | 63 | return res | ||
88 | 64 | |||
89 | 65 | |||
90 | 54 | def _get_analytic_line(self, cr, uid, ids, context=None): | 66 | def _get_analytic_line(self, cr, uid, ids, context=None): |
91 | 55 | result = [] | 67 | result = [] |
92 | 56 | for aal in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context): | 68 | for aal in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context): |
93 | 57 | if aal.task_id: result.append(aal.task_id.id) | 69 | if aal.task_id: result.append(aal.task_id.id) |
94 | 58 | return result | 70 | return result |
95 | 59 | 71 | ||
120 | 60 | 72 | _columns = { | |
121 | 61 | _columns = {'work_ids': fields.one2many('hr.analytic.timesheet', 'task_id', 'Work done'), | 73 | 'work_ids': fields.one2many('hr.analytic.timesheet', 'task_id', 'Work done'), |
122 | 62 | 74 | 'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', | |
123 | 63 | 75 | help="Computed using the sum of the task work done (timesheet lines " | |
124 | 64 | 'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', | 76 | "associated on this task).", |
125 | 65 | help="Sum of spent hours of all tasks related to this project and its child projects.", | 77 | store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20), |
126 | 66 | store={'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20), | 78 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), |
127 | 67 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), | 79 | 'delay_hours': fields.function(_progress_rate, multi="progress", string='Deduced Hours', |
128 | 68 | 80 | help="Computed as difference between planned hours by the project manager " | |
129 | 69 | 'delay_hours': fields.function(_progress_rate, multi="progress", string='Deduced Hours', | 81 | "and the total hours of the task.", |
130 | 70 | help="Sum of spent hours with invoice factor of all tasks related to this project and its child projects.", | 82 | store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20), |
131 | 71 | store={'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20), | 83 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), |
132 | 72 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), | 84 | 'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', |
133 | 73 | 85 | help="Computed as: Time Spent + Remaining Time.", | |
134 | 74 | 'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', | 86 | store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20), |
135 | 75 | help="Sum of total hours of all tasks related to this project and its child projects.", | 87 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), |
136 | 76 | store={'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20), | 88 | 'progress': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", |
137 | 77 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}), | 89 | help="If the task has a progress of 99.99% you should close the task if it's " |
138 | 78 | 90 | "finished or reevaluate the time", | |
139 | 79 | 'progress': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", | 91 | store={'project.task': (lambda self, cr, uid, ids, c=None: ids, TASK_WATCHERS, 20), |
140 | 80 | help="Percent of tasks closed according to the total of tasks todo.", | 92 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)}) |
141 | 81 | store={'project.task': (lambda self, cr, uid, ids, c={}: ids, TASK_WATCHERS, 20), | 93 | } |
142 | 82 | 'account.analytic.line': (_get_analytic_line, TIMESHEET_WATCHERS, 20)})} | 94 | |
119 | 83 | |||
143 | 84 | def write(self, cr, uid, ids, vals, context=None): | 95 | def write(self, cr, uid, ids, vals, context=None): |
144 | 85 | res = super(ProjectTask, self).write(cr, uid, ids, vals, context=context) | 96 | res = super(ProjectTask, self).write(cr, uid, ids, vals, context=context) |
145 | 86 | if vals.get('project_id'): | 97 | if vals.get('project_id'): |
146 | 87 | ts_obj = self.pool.get('hr.analytic.timesheet') | 98 | ts_obj = self.pool.get('hr.analytic.timesheet') |
147 | 88 | project_obj = self.pool.get('project.project') | 99 | project_obj = self.pool.get('project.project') |
149 | 89 | project = project_obj.browse(cr, uid, vals['project_id'], context) | 100 | project = project_obj.browse(cr, uid, vals['project_id'], context=context) |
150 | 90 | account_id = project.analytic_account_id.id | 101 | account_id = project.analytic_account_id.id |
151 | 91 | for task in self.browse(cr, uid, ids, context=context): | 102 | for task in self.browse(cr, uid, ids, context=context): |
152 | 92 | ts_obj.write(cr, uid, [w.id for w in task.work_ids], {'account_id': account_id}, context=context) | 103 | ts_obj.write(cr, uid, [w.id for w in task.work_ids], {'account_id': account_id}, context=context) |
153 | 93 | return res | 104 | return res |
154 | 94 | 105 | ||
155 | 106 | |||
156 | 95 | class HrAnalyticTimesheet(orm.Model): | 107 | class HrAnalyticTimesheet(orm.Model): |
157 | 96 | """ | 108 | """ |
158 | 97 | Add field: | 109 | Add field: |
159 | @@ -123,7 +135,8 @@ | |||
160 | 123 | _name = "hr.analytic.timesheet" | 135 | _name = "hr.analytic.timesheet" |
161 | 124 | 136 | ||
162 | 125 | def on_change_unit_amount(self, cr, uid, sheet_id, prod_id, unit_amount, company_id, | 137 | def on_change_unit_amount(self, cr, uid, sheet_id, prod_id, unit_amount, company_id, |
164 | 126 | unit=False, journal_id=False, task_id=False, to_invoice=False, context=None): | 138 | unit=False, journal_id=False, task_id=False, to_invoice=False, |
165 | 139 | context=None): | ||
166 | 127 | res = super(HrAnalyticTimesheet, self).on_change_unit_amount(cr, | 140 | res = super(HrAnalyticTimesheet, self).on_change_unit_amount(cr, |
167 | 128 | uid, | 141 | uid, |
168 | 129 | sheet_id, | 142 | sheet_id, |
169 | @@ -149,16 +162,17 @@ | |||
170 | 149 | return dict.fromkeys(ids, False) | 162 | return dict.fromkeys(ids, False) |
171 | 150 | 163 | ||
172 | 151 | _columns = { | 164 | _columns = { |
174 | 152 | 'hr_analytic_timesheet_id': fields.function(_get_dummy_hr_analytic_timesheet_id, string='Related Timeline Id', type='boolean') | 165 | 'hr_analytic_timesheet_id': fields.function(_get_dummy_hr_analytic_timesheet_id, |
175 | 166 | string='Related Timeline Id', | ||
176 | 167 | type='boolean') | ||
177 | 153 | } | 168 | } |
178 | 154 | 169 | ||
179 | 170 | |||
180 | 155 | class AccountAnalyticLine(orm.Model): | 171 | class AccountAnalyticLine(orm.Model): |
181 | 156 | """We add task_id on AA and manage update of linked task indicators""" | 172 | """We add task_id on AA and manage update of linked task indicators""" |
182 | 157 | _inherit = "account.analytic.line" | 173 | _inherit = "account.analytic.line" |
183 | 158 | _name = "account.analytic.line" | 174 | _name = "account.analytic.line" |
184 | 159 | 175 | ||
185 | 160 | |||
186 | 161 | |||
187 | 162 | _columns = {'task_id': fields.many2one('project.task', 'Task')} | 176 | _columns = {'task_id': fields.many2one('project.task', 'Task')} |
188 | 163 | 177 | ||
189 | 164 | def _check_task_project(self, cr, uid, ids): | 178 | def _check_task_project(self, cr, uid, ids): |
190 | @@ -172,22 +186,22 @@ | |||
191 | 172 | (_check_task_project, 'Error! Task must belong to the project.', ['task_id','account_id']), | 186 | (_check_task_project, 'Error! Task must belong to the project.', ['task_id','account_id']), |
192 | 173 | ] | 187 | ] |
193 | 174 | 188 | ||
200 | 175 | def _compute_hours_with_factor(self, cr, uid, hours, factor_id, context=None): | 189 | |
201 | 176 | if not hours or not factor_id: | 190 | def _trigger_projects(self, cr, uid, task_ids, context=None): |
202 | 177 | return 0.0 | 191 | t_obj = self.pool['project.task'] |
203 | 178 | fact_obj = self.pool.get('hr_timesheet_invoice.factor') | 192 | for task in t_obj.browse(cr, SUPERUSER_ID, task_ids, context=context): |
204 | 179 | factor = 100.0 - float(fact_obj.browse(cr, uid, factor_id).factor) | 193 | project = task.project_id |
205 | 180 | return (float(hours) / 100.00) * factor | 194 | project.write({'parent_id': project.parent_id.id}) |
206 | 195 | return task_ids | ||
207 | 181 | 196 | ||
208 | 182 | def _set_remaining_hours_create(self, cr, uid, vals, context=None): | 197 | def _set_remaining_hours_create(self, cr, uid, vals, context=None): |
209 | 183 | if not vals.get('task_id'): | 198 | if not vals.get('task_id'): |
210 | 184 | return | 199 | return |
211 | 185 | hours = vals.get('unit_amount', 0.0) | 200 | hours = vals.get('unit_amount', 0.0) |
217 | 186 | factor_id = vals.get('to_invoice') | 201 | # We can not do a write else we will have a recursion error |
218 | 187 | comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context) | 202 | cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', |
219 | 188 | if comp_hours: | 203 | (hours, vals['task_id'])) |
220 | 189 | cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', | 204 | self._trigger_projects(cr, uid, [vals['task_id']], context=context) |
216 | 190 | (comp_hours, vals['task_id'])) | ||
221 | 191 | return vals | 205 | return vals |
222 | 192 | 206 | ||
223 | 193 | def _set_remaining_hours_write(self, cr, uid, ids, vals, context=None): | 207 | def _set_remaining_hours_write(self, cr, uid, ids, vals, context=None): |
224 | @@ -196,8 +210,8 @@ | |||
225 | 196 | for line in self.browse(cr, uid, ids): | 210 | for line in self.browse(cr, uid, ids): |
226 | 197 | # in OpenERP if we set a value to nil vals become False | 211 | # in OpenERP if we set a value to nil vals become False |
227 | 198 | old_task_id = line.task_id and line.task_id.id or None | 212 | old_task_id = line.task_id and line.task_id.id or None |
230 | 199 | new_task_id = vals.get('task_id', old_task_id) # if no task_id in vals we assume it is equal to old | 213 | # if no task_id in vals we assume it is equal to old |
231 | 200 | 214 | new_task_id = vals.get('task_id', old_task_id) | |
232 | 201 | # we look if value has changed | 215 | # we look if value has changed |
233 | 202 | if (new_task_id != old_task_id) and old_task_id: | 216 | if (new_task_id != old_task_id) and old_task_id: |
234 | 203 | self._set_remaining_hours_unlink(cr, uid, [line.id], context) | 217 | self._set_remaining_hours_unlink(cr, uid, [line.id], context) |
235 | @@ -207,20 +221,19 @@ | |||
236 | 207 | line.to_invoice and line.to_invoice.id or False), | 221 | line.to_invoice and line.to_invoice.id or False), |
237 | 208 | 'unit_amount': vals.get('unit_amount', line.unit_amount)} | 222 | 'unit_amount': vals.get('unit_amount', line.unit_amount)} |
238 | 209 | self._set_remaining_hours_create(cr, uid, data, context) | 223 | self._set_remaining_hours_create(cr, uid, data, context) |
239 | 224 | self._trigger_projects(cr, uid, list(set([old_task_id, new_task_id])), | ||
240 | 225 | context=context) | ||
241 | 210 | return ids | 226 | return ids |
242 | 211 | if new_task_id: | 227 | if new_task_id: |
243 | 212 | hours = vals.get('unit_amount', line.unit_amount) | 228 | hours = vals.get('unit_amount', line.unit_amount) |
250 | 213 | factor_id = vals.get('to_invoice', line.to_invoice and line.to_invoice.id or False) | 229 | old_hours = line.unit_amount if old_task_id else 0.0 |
251 | 214 | comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context) | 230 | # We can not do a write else we will have a recursion error |
246 | 215 | old_factor = line.to_invoice and line.to_invoice.id or False | ||
247 | 216 | old_comp_hours = self._compute_hours_with_factor(cr, uid, line.unit_amount, | ||
248 | 217 | old_factor, context) | ||
249 | 218 | # we always execute request because invoice factor can be set to gratis | ||
252 | 219 | cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', | 231 | cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', |
254 | 220 | (comp_hours, old_comp_hours, new_task_id)) | 232 | (hours, old_hours, new_task_id)) |
255 | 233 | self._trigger_projects(cr, uid, [new_task_id], context=context) | ||
256 | 234 | |||
257 | 221 | return ids | 235 | return ids |
258 | 222 | 236 | ||
259 | 223 | |||
260 | 224 | def _set_remaining_hours_unlink(self, cr, uid, ids, context=None): | 237 | def _set_remaining_hours_unlink(self, cr, uid, ids, context=None): |
261 | 225 | if isinstance(ids, (int, long)): | 238 | if isinstance(ids, (int, long)): |
262 | 226 | ids = [ids] | 239 | ids = [ids] |
263 | @@ -228,15 +241,10 @@ | |||
264 | 228 | if not line.task_id: | 241 | if not line.task_id: |
265 | 229 | continue | 242 | continue |
266 | 230 | hours = line.unit_amount or 0.0 | 243 | hours = line.unit_amount or 0.0 |
272 | 231 | factor_id = line.to_invoice and line.to_invoice.id or False | 244 | cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', |
273 | 232 | comp_hours = self._compute_hours_with_factor(cr, uid, hours, factor_id, context) | 245 | (hours, line.task_id.id)) |
269 | 233 | if comp_hours: | ||
270 | 234 | cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', | ||
271 | 235 | (comp_hours, line.task_id.id)) | ||
274 | 236 | return ids | 246 | return ids |
275 | 237 | 247 | ||
276 | 238 | |||
277 | 239 | |||
278 | 240 | def create(self, cr, uid, vals, context=None): | 248 | def create(self, cr, uid, vals, context=None): |
279 | 241 | if vals.get('task_id'): | 249 | if vals.get('task_id'): |
280 | 242 | self._set_remaining_hours_create(cr, uid, vals, context) | 250 | self._set_remaining_hours_create(cr, uid, vals, context) |
281 | 243 | 251 | ||
282 | === added directory 'timesheet_task/test' | |||
283 | === added file 'timesheet_task/test/task_timesheet_indicators.yml' | |||
284 | --- timesheet_task/test/task_timesheet_indicators.yml 1970-01-01 00:00:00 +0000 | |||
285 | +++ timesheet_task/test/task_timesheet_indicators.yml 2013-10-08 11:06:42 +0000 | |||
286 | @@ -0,0 +1,282 @@ | |||
287 | 1 | - | ||
288 | 2 | Create a user 'HR Tester' | ||
289 | 3 | - | ||
290 | 4 | !record {model: res.users, id: res_users_hrtester0}: | ||
291 | 5 | company_id: base.main_company | ||
292 | 6 | name: HR Tester | ||
293 | 7 | login: hr | ||
294 | 8 | password: hr | ||
295 | 9 | groups_id: | ||
296 | 10 | - base.group_hr_manager | ||
297 | 11 | - | ||
298 | 12 | Create a product with type service used to specify employees designation | ||
299 | 13 | - | ||
300 | 14 | !record {model: product.product, id: product_product_hrtester0}: | ||
301 | 15 | categ_id: product.product_category_6 | ||
302 | 16 | cost_method: standard | ||
303 | 17 | mes_type: fixed | ||
304 | 18 | name: HR Tester service | ||
305 | 19 | standard_price: 10.0 | ||
306 | 20 | type: service | ||
307 | 21 | uom_id: product.product_uom_hour | ||
308 | 22 | uom_po_id: product.product_uom_hour | ||
309 | 23 | volume: 0.0 | ||
310 | 24 | warranty: 0.0 | ||
311 | 25 | weight: 0.0 | ||
312 | 26 | weight_net: 0.0 | ||
313 | 27 | - | ||
314 | 28 | Create an analytic journal for employees timesheet | ||
315 | 29 | - | ||
316 | 30 | !record {model: account.analytic.journal, id: account_analytic_journal_hrtimesheettest0}: | ||
317 | 31 | company_id: base.main_company | ||
318 | 32 | name: HR Timesheet test | ||
319 | 33 | type: general | ||
320 | 34 | - | ||
321 | 35 | Create an employee 'HR Tester' for user 'HR Tester' | ||
322 | 36 | - | ||
323 | 37 | !record {model: hr.employee, id: hr_employee_hrtester0}: | ||
324 | 38 | name: HR Tester | ||
325 | 39 | user_id: res_users_hrtester0 | ||
326 | 40 | product_id: product_product_hrtester0 | ||
327 | 41 | journal_id: account_analytic_journal_hrtimesheettest0 | ||
328 | 42 | - | ||
329 | 43 | Create a timesheet invoice factor of 100% | ||
330 | 44 | - | ||
331 | 45 | !record {model: hr_timesheet_invoice.factor, id: timesheet_invoice_factor0}: | ||
332 | 46 | name: 100% | ||
333 | 47 | customer_name: 100% | ||
334 | 48 | factor: 0.0 | ||
335 | 49 | - | ||
336 | 50 | Create a project 'Timesheet task and indicator tests' | ||
337 | 51 | - | ||
338 | 52 | !record {model: project.project, id: project_project_timesheettest0}: | ||
339 | 53 | company_id: base.main_company | ||
340 | 54 | name: Timesheet task and indicator tests | ||
341 | 55 | to_invoice: timesheet_invoice_factor0 | ||
342 | 56 | - | ||
343 | 57 | Create a task 'Test timesheet records' | ||
344 | 58 | - | ||
345 | 59 | !record {model: project.task, id: project_task_testtimesheetrecords0}: | ||
346 | 60 | date_start: !eval time.strftime('%Y-05-%d %H:%M:%S') | ||
347 | 61 | name: Test timesheet records | ||
348 | 62 | planned_hours: 200.0 | ||
349 | 63 | project_id: project_project_timesheettest0 | ||
350 | 64 | user_id: res_users_hrtester0 | ||
351 | 65 | - | ||
352 | 66 | The planned time on project should then be 200 | ||
353 | 67 | - | ||
354 | 68 | !assert {model: project.project, id: project_project_timesheettest0, string: planned_time_on_project_01}: | ||
355 | 69 | - planned_hours == 200.0 | ||
356 | 70 | - | ||
357 | 71 | The time spent on project should then be 0 | ||
358 | 72 | - | ||
359 | 73 | !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_01}: | ||
360 | 74 | - effective_hours == 0.0 | ||
361 | 75 | - | ||
362 | 76 | Make a timesheet line of 10.0 on the project without task | ||
363 | 77 | - | ||
364 | 78 | !python {model: hr.analytic.timesheet}: | | ||
365 | 79 | import time | ||
366 | 80 | project_obj = self.pool.get('project.project') | ||
367 | 81 | project = project_obj.browse(cr, uid, ref('project_project_timesheettest0')) | ||
368 | 82 | ts_line= { | ||
369 | 83 | 'name': '/', | ||
370 | 84 | 'user_id': ref('res_users_hrtester0'), | ||
371 | 85 | 'date': time.strftime('%Y-%m-%d'), | ||
372 | 86 | 'account_id': project.analytic_account_id.id, | ||
373 | 87 | 'unit_amount': 10.0, | ||
374 | 88 | 'journal_id': ref('account_analytic_journal_hrtimesheettest0'), | ||
375 | 89 | 'to_invoice': ref('timesheet_invoice_factor0'), | ||
376 | 90 | } | ||
377 | 91 | ts = self.create(cr, uid, ts_line) | ||
378 | 92 | assert ts, "Timesheet has not been recorded correctly" | ||
379 | 93 | - | ||
380 | 94 | The time spent on project should still be 0 as no task has been set | ||
381 | 95 | - | ||
382 | 96 | !assert {model: project.project, id: project_project_timesheettest0}: | ||
383 | 97 | - effective_hours == 0.0 | ||
384 | 98 | - | ||
385 | 99 | Make a timesheet line of 10.0 on the project with a task assigned | ||
386 | 100 | - | ||
387 | 101 | !python {model: hr.analytic.timesheet}: | | ||
388 | 102 | import time | ||
389 | 103 | project_obj = self.pool.get('project.project') | ||
390 | 104 | project = project_obj.browse(cr, uid, ref('project_project_timesheettest0')) | ||
391 | 105 | ts_line= { | ||
392 | 106 | 'name': '/', | ||
393 | 107 | 'user_id': ref('res_users_hrtester0'), | ||
394 | 108 | 'date': time.strftime('%Y-%m-%d'), | ||
395 | 109 | 'account_id': project.analytic_account_id.id, | ||
396 | 110 | 'unit_amount': 10.0, | ||
397 | 111 | 'task_id': ref('project_task_testtimesheetrecords0'), | ||
398 | 112 | 'to_invoice': ref('timesheet_invoice_factor0'), | ||
399 | 113 | 'journal_id': ref('account_analytic_journal_hrtimesheettest0'), | ||
400 | 114 | } | ||
401 | 115 | ts = self.create(cr, uid, ts_line) | ||
402 | 116 | assert ts, "Timesheet has not been recorded correctly" | ||
403 | 117 | - | ||
404 | 118 | The time spent on the task should be 10.0 | ||
405 | 119 | - | ||
406 | 120 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_01}: | ||
407 | 121 | - effective_hours == 10.0 | ||
408 | 122 | - | ||
409 | 123 | The remaining time on the task should be 190.0 | ||
410 | 124 | - | ||
411 | 125 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_01}: | ||
412 | 126 | - remaining_hours == 190.0 | ||
413 | 127 | - | ||
414 | 128 | The time spent on project should be 10.0 | ||
415 | 129 | - | ||
416 | 130 | !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_02}: | ||
417 | 131 | - effective_hours == 10.0 | ||
418 | 132 | - | ||
419 | 133 | Make a timesheet line of 10.0 on the project without task, then assign one | ||
420 | 134 | - | ||
421 | 135 | !python {model: hr.analytic.timesheet}: | | ||
422 | 136 | import time | ||
423 | 137 | project_obj = self.pool.get('project.project') | ||
424 | 138 | project = project_obj.browse(cr, uid, ref('project_project_timesheettest0')) | ||
425 | 139 | ts_line= { | ||
426 | 140 | 'name': '/', | ||
427 | 141 | 'user_id': ref('res_users_hrtester0'), | ||
428 | 142 | 'date': time.strftime('%Y-%m-%d'), | ||
429 | 143 | 'account_id': project.analytic_account_id.id, | ||
430 | 144 | 'unit_amount': 10.0, | ||
431 | 145 | 'to_invoice': ref('timesheet_invoice_factor0'), | ||
432 | 146 | 'journal_id': ref('account_analytic_journal_hrtimesheettest0'), | ||
433 | 147 | } | ||
434 | 148 | ts = self.create(cr, uid, ts_line) | ||
435 | 149 | assert ts, "Timesheet has not been recorded correctly" | ||
436 | 150 | vals = { | ||
437 | 151 | 'task_id': ref('project_task_testtimesheetrecords0') | ||
438 | 152 | } | ||
439 | 153 | result = self.write(cr, uid, ts, vals) | ||
440 | 154 | - | ||
441 | 155 | The time spent on the task should be 20.0 | ||
442 | 156 | - | ||
443 | 157 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_02}: | ||
444 | 158 | - effective_hours == 20.0 | ||
445 | 159 | - | ||
446 | 160 | The remaining time on the task should be 180.0 | ||
447 | 161 | - | ||
448 | 162 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_02}: | ||
449 | 163 | - remaining_hours == 180.0 | ||
450 | 164 | - | ||
451 | 165 | The time spent on project should be 20.0 | ||
452 | 166 | - | ||
453 | 167 | !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_03}: | ||
454 | 168 | - effective_hours == 20.0 | ||
455 | 169 | - | ||
456 | 170 | Make a timesheet line of 10.0 with task, then remove the task | ||
457 | 171 | - | ||
458 | 172 | !python {model: hr.analytic.timesheet}: | | ||
459 | 173 | import time | ||
460 | 174 | project_obj = self.pool.get('project.project') | ||
461 | 175 | project = project_obj.browse(cr, uid, ref('project_project_timesheettest0')) | ||
462 | 176 | task_obj = self.pool.get('project.task') | ||
463 | 177 | ts_line= { | ||
464 | 178 | 'name': '/', | ||
465 | 179 | 'user_id': ref('res_users_hrtester0'), | ||
466 | 180 | 'date': time.strftime('%Y-%m-%d'), | ||
467 | 181 | 'account_id': project.analytic_account_id.id, | ||
468 | 182 | 'unit_amount': 10.0, | ||
469 | 183 | 'task_id': ref('project_task_testtimesheetrecords0'), | ||
470 | 184 | 'to_invoice': ref('timesheet_invoice_factor0'), | ||
471 | 185 | 'journal_id': ref('account_analytic_journal_hrtimesheettest0'), | ||
472 | 186 | } | ||
473 | 187 | ts = self.create(cr, uid, ts_line) | ||
474 | 188 | assert ts, "Timesheet has not been recorded correctly" | ||
475 | 189 | task = task_obj.browse(cr, uid, ref('project_task_testtimesheetrecords0')) | ||
476 | 190 | assert task.effective_hours == 30.0, "Effective hours on task is not correct" | ||
477 | 191 | assert task.remaining_hours == 170.0, "Remaining hours on task is not correct" | ||
478 | 192 | project.refresh() | ||
479 | 193 | assert project.effective_hours == 30.0, "Effective hours on project is not correct" | ||
480 | 194 | vals = { | ||
481 | 195 | 'task_id': False | ||
482 | 196 | } | ||
483 | 197 | result = self.write(cr, uid, ts, vals) | ||
484 | 198 | - | ||
485 | 199 | The time spent on the task should be 20.0 | ||
486 | 200 | - | ||
487 | 201 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_03}: | ||
488 | 202 | - effective_hours == 20.0 | ||
489 | 203 | - | ||
490 | 204 | The remaining time on the task should be 180.0 | ||
491 | 205 | - | ||
492 | 206 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_03}: | ||
493 | 207 | - remaining_hours == 180.0 | ||
494 | 208 | - | ||
495 | 209 | The time spent on project should be 20.0 | ||
496 | 210 | - | ||
497 | 211 | !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_04}: | ||
498 | 212 | - effective_hours == 20.0 | ||
499 | 213 | - | ||
500 | 214 | Make a timesheet line of 10.0 with task, then delete the line | ||
501 | 215 | - | ||
502 | 216 | !python {model: hr.analytic.timesheet}: | | ||
503 | 217 | import time | ||
504 | 218 | project_obj = self.pool.get('project.project') | ||
505 | 219 | project = project_obj.browse(cr, uid, ref('project_project_timesheettest0')) | ||
506 | 220 | task_obj = self.pool.get('project.task') | ||
507 | 221 | ts_line= { | ||
508 | 222 | 'name': '/', | ||
509 | 223 | 'user_id': ref('res_users_hrtester0'), | ||
510 | 224 | 'date': time.strftime('%Y-%m-%d'), | ||
511 | 225 | 'account_id': project.analytic_account_id.id, | ||
512 | 226 | 'unit_amount': 10.0, | ||
513 | 227 | 'task_id': ref('project_task_testtimesheetrecords0'), | ||
514 | 228 | 'to_invoice': ref('timesheet_invoice_factor0'), | ||
515 | 229 | 'journal_id': ref('account_analytic_journal_hrtimesheettest0'), | ||
516 | 230 | } | ||
517 | 231 | ts = self.create(cr, uid, ts_line) | ||
518 | 232 | assert ts, "Timesheet has not been recorded correctly" | ||
519 | 233 | task = task_obj.browse(cr, uid, ref('project_task_testtimesheetrecords0')) | ||
520 | 234 | assert task.effective_hours == 30.0, "Effective hours on task is not correct" | ||
521 | 235 | assert task.remaining_hours == 170.0, "Remaining hours on task is not correct" | ||
522 | 236 | project.refresh() | ||
523 | 237 | assert project.effective_hours == 30.0, "Effective hours on project is not correct" | ||
524 | 238 | result = self.unlink(cr, uid, [ts]) | ||
525 | 239 | - | ||
526 | 240 | The time spent on the task should be 20.0 | ||
527 | 241 | - | ||
528 | 242 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_04}: | ||
529 | 243 | - effective_hours == 20.0 | ||
530 | 244 | - | ||
531 | 245 | The remaining time on the task should be 180.0 | ||
532 | 246 | - | ||
533 | 247 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_04}: | ||
534 | 248 | - remaining_hours == 180.0 | ||
535 | 249 | - | ||
536 | 250 | The time spent on project should be 20.0 | ||
537 | 251 | - | ||
538 | 252 | !assert {model: project.project, id: project_project_timesheettest0, string: time_spent_on_project_05}: | ||
539 | 253 | - effective_hours == 20.0 | ||
540 | 254 | - | ||
541 | 255 | Change the remaining hours of the task to 200.0 | ||
542 | 256 | - | ||
543 | 257 | !python {model: project.task}: | | ||
544 | 258 | task = self.browse(cr, uid, ref('project_task_testtimesheetrecords0')) | ||
545 | 259 | vals = { | ||
546 | 260 | 'remaining_hours': 200.0, | ||
547 | 261 | } | ||
548 | 262 | result = self.write(cr, uid, [task.id], vals) | ||
549 | 263 | - | ||
550 | 264 | The time spent on the task should still be 20.0 | ||
551 | 265 | - | ||
552 | 266 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: time_spent_on_task_05}: | ||
553 | 267 | - effective_hours == 20.0 | ||
554 | 268 | - | ||
555 | 269 | The remaining time on the task should be 200.0 | ||
556 | 270 | - | ||
557 | 271 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: remaining_time_on_task_05}: | ||
558 | 272 | - remaining_hours == 200.0 | ||
559 | 273 | - | ||
560 | 274 | The delay in hours on the task should be 20.0 | ||
561 | 275 | - | ||
562 | 276 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: delay_on_task_01}: | ||
563 | 277 | - delay_hours == 20.0 | ||
564 | 278 | - | ||
565 | 279 | The total on the task should be 220.0 | ||
566 | 280 | - | ||
567 | 281 | !assert {model: project.task, id: project_task_testtimesheetrecords0, string: total_time_on_task_01}: | ||
568 | 282 | - total_hours == 220.0 |
LGTM, Thanks