[IMP] project: project_get query improved
[odoo/odoo.git] / addons / project / project.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from lxml import etree
23 import mx.DateTime
24 import datetime
25 import time
26 from tools.translate import _
27 from osv import fields, osv
28 from tools.translate import _
29
30 class project_task_type(osv.osv):
31     _name = 'project.task.type'
32     _description = 'Project task type'
33     _columns = {
34         'name': fields.char('Type', required=True, size=64, translate=True),
35         'description': fields.text('Description'),
36         'sequence': fields.integer('Sequence'),
37     }
38     _order = 'sequence'
39     
40     _defaults = {
41         'sequence': lambda *args: 1
42     }
43 project_task_type()
44
45 class project(osv.osv):
46     _name = "project.project"
47     _description = "Project"
48     _inherits = {'account.analytic.account':"category_id"}
49     def _complete_name(self, cr, uid, ids, name, args, context):
50         res = {}
51         for m in self.browse(cr, uid, ids, context=context):
52             res[m.id] = (m.parent_id and (m.parent_id.name + '/') or '') + m.name
53         return res
54
55
56     def check_recursion(self, cursor, user, ids, parent=None):
57         return super(project, self).check_recursion(cursor, user, ids,
58                 parent=parent)
59
60     def onchange_partner_id(self, cr, uid, ids, part):
61         if not part:
62             return {'value':{'contact_id': False, 'pricelist_id': False}}
63         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['contact'])
64
65         pricelist = self.pool.get('res.partner').browse(cr, uid, part).property_product_pricelist.id
66         return {'value':{'contact_id': addr['contact'], 'pricelist_id': pricelist}}
67
68     def _progress_rate(self, cr, uid, ids, names, arg, context=None):
69         res = {}.fromkeys(ids, 0.0)
70         progress = {}
71         if not ids:
72             return res
73         ids2 = self.search(cr, uid, [('parent_id','child_of',ids)])
74         if ids2:
75             cr.execute('''SELECT
76                     project_id, sum(planned_hours), sum(total_hours), sum(effective_hours)
77                 FROM
78                     project_task
79                 WHERE
80                     project_id in ('''+','.join(map(str,ids2))+''') AND
81                     state<>'cancelled'
82                 GROUP BY
83                     project_id''')
84             progress = dict(map(lambda x: (x[0], (x[1],x[2],x[3])), cr.fetchall()))
85         for project in self.browse(cr, uid, ids, context=context):
86             s = [0.0,0.0,0.0]
87             tocompute = [project]
88             while tocompute:
89                 p = tocompute.pop()
90                 tocompute += p.child_ids
91                 for i in range(3):
92                     s[i] += progress.get(p.id, (0.0,0.0,0.0))[i]
93             res[project.id] = {
94                 'planned_hours': s[0],
95                 'effective_hours': s[2],
96                 'total_hours': s[1],
97                 'progress_rate': s[1] and (100.0 * s[2] / s[1]) or 0.0
98             }
99         return res
100
101     def unlink(self, cr, uid, ids, *args, **kwargs):
102         for proj in self.browse(cr, uid, ids):
103             if proj.tasks:
104                 raise osv.except_osv(_('Operation Not Permitted !'), _('You can not delete a project with tasks. I suggest you to deactivate it.'))
105         return super(project, self).unlink(cr, uid, ids, *args, **kwargs)
106     _columns = {
107 #       'name': fields.char("Project Name", size=128, required=True),
108 #       'complete_name': fields.function(_complete_name, method=True, string="Project Name", type='char', size=128),
109         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the project without removing it."),
110         'category_id': fields.many2one('account.analytic.account','Analytic Account', help="Link this project to an analytic account if you need financial management on projects. It enables you to connect projects with budgets, planning, cost and revenue analysis, timesheets on projects, etc."),
111         'priority': fields.integer('Sequence'),
112 #       'manager': fields.many2one('res.users', 'Project Manager'),
113         'warn_manager': fields.boolean('Warn Manager', help="If you check this field, the project manager will receive a request each time a task is completed by his team."),
114         'resource_ids': fields.many2many('resource.resource', 'project_resource_rel', 'project_id', 'resource_id', 'Project Members', help="Project's member. Not used in any computation, just for information purpose."),
115         'tasks': fields.one2many('project.task', 'project_id', "Project tasks"),
116 #        'parent_id': fields.many2one('project.project', 'Parent Project',\
117 #            help="If you have [?] in the name, it means there are no analytic account linked to project."),
118 #        'child_id': fields.one2many('project.project', 'parent_id', 'Subproject'),
119         'planned_hours': fields.function(_progress_rate, multi="progress", method=True, string='Planned Time', help="Sum of planned hours of all tasks related to this project."),
120         'effective_hours': fields.function(_progress_rate, multi="progress", method=True, string='Time Spent', help="Sum of spent hours of all tasks related to this project."),
121         'total_hours': fields.function(_progress_rate, multi="progress", method=True, string='Total Time', help="Sum of total hours of all tasks related to this project."),
122         'progress_rate': fields.function(_progress_rate, multi="progress", method=True, string='Progress', type='float', help="Percent of tasks closed according to the total of tasks todo."),
123 #        'partner_id': fields.many2one('res.partner', 'Partner'),
124 #        'contact_id': fields.many2one('res.partner.address', 'Contact'),
125         'warn_customer': fields.boolean('Warn Partner', help="If you check this, the user will have a popup when closing a task that propose a message to send by email to the customer."),
126         'warn_header': fields.text('Mail Header', help="Header added at the beginning of the email for the warning message sent to the customer when a task is closed."),
127         'warn_footer': fields.text('Mail Footer', help="Footer added at the beginning of the email for the warning message sent to the customer when a task is closed."),
128 #        'notes': fields.text('Notes', help="Internal description of the project."),
129         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"),
130 #        'state': fields.selection([('template', 'Template'), ('open', 'Running'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', required=True, readonly=True,
131 #                                  help='The project can be in either if the states \'Template\' and \'Running\'.\n If it is template then we can make projects based on the template projects. If its in \'Running\' state it is a normal project.\
132 #                                 \n If it is to be reviewed then the state is \'Pending\'.\n When the project is completed the state is set to \'Done\'.'),
133 #        'company_id': fields.many2one('res.company', 'Company'),
134 #        'timesheet_id': fields.many2one('hr.timesheet.group', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"),
135         'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Allowed Task Types'),
136      }
137
138     _defaults = {
139         'active': lambda *a: True,
140 #        'manager': lambda object,cr,uid,context: uid,
141         'priority': lambda *a: 1,
142 #        'state': lambda *a: 'open',
143 #        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.project', context=c)
144     }
145     def _check_dates(self, cr, uid, ids):
146          leave = self.read(cr, uid, ids[0],['date_start','date'])
147          if leave['date_start'] and leave['date']:
148              if leave['date_start'] > leave['date']:
149                  return False
150          return True
151
152     _constraints = [
153         (_check_dates, 'Error! project start-date must be lower then project end-date.', ['date_start', 'date'])
154     ]
155
156 #    _order = "parent_id,priority,name"
157 #    _constraints = [
158 #        (check_recursion, 'Error ! You can not create recursive projects.', ['parent_id'])
159 #    ]
160
161     # toggle activity of projects, their sub projects and their tasks
162     def set_template(self, cr, uid, ids, context={}):
163         res = self.setActive(cr, uid, ids, value=False, context=context)
164         return res
165
166     def set_done(self, cr, uid, ids, context={}):
167         self.write(cr, uid, ids, {'state':'close'}, context=context)
168         return True
169
170     def set_cancel(self, cr, uid, ids, context={}):
171         self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
172         return True
173
174     def set_pending(self, cr, uid, ids, context={}):
175         self.write(cr, uid, ids, {'state':'pending'}, context=context)
176         return True
177
178     def set_open(self, cr, uid, ids, context={}):
179         self.write(cr, uid, ids, {'state':'open'}, context=context)
180         return True
181
182     def reset_project(self, cr, uid, ids, context={}):
183         res = self.setActive(cr, uid, ids,value=True, context=context)
184         return res
185
186     def copy(self, cr, uid, id, default={},context={}):
187         proj = self.browse(cr, uid, id, context=context)
188         default = default or {}
189         context['active_test'] = False
190         default['state'] = 'open'
191         if not default.get('name', False):
192             default['name'] = proj.name+_(' (copy)')
193         res = super(project, self).copy(cr, uid, id, default, context)
194         ids = self.search(cr, uid, [('parent_id','child_of', [res])])
195         if ids:
196             cr.execute('update project_task set active=True where project_id in ('+','.join(map(str, ids))+')')
197         return res
198
199     def duplicate_template(self, cr, uid, ids,context={}):
200         result = []
201         for proj in self.browse(cr, uid, ids):
202             parent_id = context.get('parent_id',False)
203             new_id = self.pool.get('project.project').copy(cr, uid, proj.id, default = {
204                                     'name': proj.name +_(' (copy)'),
205                                     'state':'open',
206                                     'parent_id':parent_id})
207             result.append(new_id)
208             cr.execute('select id from project_task where project_id=%s', (proj.id,))
209             res = cr.fetchall()
210             for (tasks_id,) in res:
211                 self.pool.get('project.task').copy(cr, uid, tasks_id, default = {
212                                     'project_id': new_id,
213                                     'active':True}, context=context)
214             child_ids = self.search(cr, uid, [('parent_id','=', proj.id)])            
215             if child_ids:
216                 self.duplicate_template(cr, uid, child_ids, context={'parent_id':new_id})
217         return result
218
219     # set active value for a project, its sub projects and its tasks
220     def setActive(self, cr, uid, ids, value=True, context={}):
221         for proj in self.browse(cr, uid, ids, context):
222             self.write(cr, uid, [proj.id], {'state': value and 'open' or 'template'}, context)
223             cr.execute('select id from project_task where project_id=%s', (proj.id,))
224             tasks_id = [x[0] for x in cr.fetchall()]
225             if tasks_id:
226                 self.pool.get('project.task').write(cr, uid, tasks_id, {'active': value}, context)
227             child_ids = self.search(cr, uid, [('parent_id','=', proj.id)]) 
228             if child_ids:
229                 self.setActive(cr, uid, child_ids, value, context)
230         return True
231 project()
232
233 class task(osv.osv):
234     _name = "project.task"
235     _description = "Tasks"
236     _date_name = "date_start"
237
238 #    def compute_date(self,cr,uid):
239 #        project_id = self.pool.get('project.project').search(cr,uid,[])
240 #        for i in range(len(project_id)):
241 #            task_ids = self.pool.get('project.task').search(cr,uid,[('project_id','=',project_id[i])])
242 #            if task_ids:
243 #                task_obj = self.pool.get('project.task').browse(cr,uid,task_ids)
244 #                task_1 = task_obj[0]
245 #                task_1.date_start = self.pool.get('project.project').browse(cr,uid,project_id[i]).date_start
246 #                dt = mx.DateTime.strptime(task_1.date_start,"%Y-%m-%d").strftime("%Y-%m-%d")
247 #                def Project_1():
248 #                   title = "New Project"
249 #                   start = dt
250 #
251 #                   def task1():
252 #                       start = dt
253 #                       effort = task_1.planned_hours
254 #                       title = "Task 1"
255 ##                project_1 = BalancedProject(Project_1)
256 ##                for t in project_1:
257 ##                    print 'details:::',t.indent_name(), t.start, t.end, t.effort
258
259
260     def _str_get(self, task, level=0, border='***', context={}):
261         return border+' '+(task.user_id and task.user_id.name.upper() or '')+(level and (': L'+str(level)) or '')+(' - %.1fh / %.1fh'%(task.effective_hours or 0.0,task.planned_hours))+' '+border+'\n'+ \
262             border[0]+' '+(task.name or '')+'\n'+ \
263             (task.description or '')+'\n\n'
264
265     def _history_get(self, cr, uid, ids, name, args, context={}):
266         result = {}
267         for task in self.browse(cr, uid, ids, context=context):
268             result[task.id] = self._str_get(task, border='===')
269             t2 = task.parent_ids
270             level = 0
271             while t2:
272                 level -= 1
273                 result[task.id] = self._str_get(t2, level) + result[task.id]
274                 t2 = t2.parent_ids
275             t3 = map(lambda x: (x,1), task.child_ids)
276             while t3:
277                 t2 = t3.pop(0)
278                 result[task.id] = result[task.id] + self._str_get(t2[0], t2[1])
279                 t3 += map(lambda x: (x,t2[1]+1), t2[0].child_ids)
280         return result
281
282 # Compute: effective_hours, total_hours, progress
283     def _hours_get(self, cr, uid, ids, field_names, args, context):
284         task_set = ','.join(map(str, ids))
285         cr.execute(("SELECT task_id, COALESCE(SUM(hours),0) FROM project_task_work WHERE task_id in (%s) GROUP BY task_id") % (task_set,))
286         hours = dict(cr.fetchall())
287         res = {}
288         for task in self.browse(cr, uid, ids, context=context):
289             res[task.id] = {}
290             res[task.id]['effective_hours'] = hours.get(task.id, 0.0)
291             res[task.id]['total_hours'] = task.remaining_hours + hours.get(task.id, 0.0)
292             if (task.remaining_hours + hours.get(task.id, 0.0)):
293                 res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 100),2)
294             else:
295                 res[task.id]['progress'] = 0.0
296             res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours
297         return res
298
299     def onchange_planned(self, cr, uid, ids, planned, effective, date_start,occupation_rate=0.0):
300         result = {}
301         for res in self.browse(cr, uid, ids):
302             if date_start and planned:
303                 resource_id = self.pool.get('resource.resource').search(cr,uid,[('user_id','=',res.user_id.id)])
304                 resource_obj = self.pool.get('resource.resource').browse(cr,uid,resource_id)[0]
305                 d = mx.DateTime.strptime(date_start,'%Y-%m-%d %H:%M:%S')
306                 hrs = (planned)/(occupation_rate)
307                 work_times = self.pool.get('resource.calendar').interval_get(cr, uid, resource_obj.calendar_id.id or False, d, hrs or 0.0, resource_obj.id)
308                 result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
309         result['remaining_hours'] = planned-effective
310         return {'value':result}
311
312
313     def _default_project(self, cr, uid, context={}):
314         if 'project_id' in context and context['project_id']:
315             return context['project_id']
316         return False
317
318     #_sql_constraints = [
319     #    ('remaining_hours', 'CHECK (remaining_hours>=0)', 'Please increase and review remaining hours ! It can not be smaller than 0.'),
320     #]
321
322     def copy_data(self, cr, uid, id, default={},context={}):
323         default = default or {}
324         default['work_ids'] = []
325         return super(task, self).copy_data(cr, uid, id, default, context)
326
327         def _check_date(self,cr,uid,ids):
328             for res in self.browse(cr,uid,ids):
329                 if res.date_start and res.date_end:
330                     if res.date_start > res.date_end:
331                         return False
332                     return True
333
334     _columns = {
335         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the task without removing it."),
336         'name': fields.char('Task Summary', size=128, required=True),
337         'description': fields.text('Description'),
338         'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'), ('0','Very urgent')], 'Importance'),
339         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of tasks."),
340         'type': fields.many2one('project.task.type', 'Type', readonly=True),
341         'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
342                                   help='If the task is created the state \'Draft\'.\n If the task is started, the state becomes \'In Progress\'.\n If review is needed the task is in \'Pending\' state.\
343                                   \n If the task is over, the states is set to \'Done\'.'),
344         'date_start': fields.datetime('Starting Date'),
345         'date_end': fields.datetime('Ending Date'),
346         'date_deadline': fields.datetime('Deadline'),
347         'date_close': fields.datetime('Date Closed', readonly=True),
348         'project_id': fields.many2one('project.project', 'Project', ondelete='cascade',
349             help="If you have [?] in the project name, it means there are no analytic account linked to this project."),
350         'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
351         'child_ids': fields.many2many('project.task', 'project_task_child_rel', 'task_id', 'child_id', 'Delegated Tasks'),
352         'history': fields.function(_history_get, method=True, string="Task Details", type="text"),
353         'notes': fields.text('Notes'),
354         'occupation_rate': fields.float('Occupation Rate', help='The occupation rate fields indicates how much of his time a user is working on a task. A 100% occupation rate means the user works full time on the tasks. The ending date of a task is computed like this: Starting Date + Duration / Occupation Rate.'),
355         'planned_hours': fields.float('Planned Hours', required=True, help='Estimated time to do the task, usually set by the project manager when the task is in draft state.'),
356         'effective_hours': fields.function(_hours_get, method=True, string='Hours Spent', multi='hours', store=True, help="Computed using the sum of the task work done."),
357         'remaining_hours': fields.float('Remaining Hours', digits=(16,4), help="Total remaining time, can be re-estimated periodically by the assignee of the task."),
358         'total_hours': fields.function(_hours_get, method=True, string='Total Hours', multi='hours', store=True, help="Computed as: Time Spent + Remaining Time."),
359         'progress': fields.function(_hours_get, method=True, string='Progress (%)', multi='hours', store=True, help="Computed as: Time Spent / Total Time."),
360         'delay_hours': fields.function(_hours_get, method=True, string='Delay Hours', multi='hours', store=True, help="Computed as: Total Time - Estimated Time. It gives the difference of the time estimated by the project manager and the real time to close the task."),
361
362         'user_id': fields.many2one('res.users', 'Assigned to'),
363         'delegated_user_id': fields.related('child_ids','user_id',type='many2one', relation='res.users', string='Delegated To'),
364         'partner_id': fields.many2one('res.partner', 'Partner'),
365         'work_ids': fields.one2many('project.task.work', 'task_id', 'Work done'),
366         'manager_id': fields.related('project_id','category_id','user_id', type='many2one', relation='res.users', string='Project Manager'),
367         'company_id': fields.many2one('res.company', 'Company'),
368     }
369     _defaults = {
370         'user_id': lambda obj,cr,uid,context: uid,
371         'state': lambda *a: 'draft',
372         'priority': lambda *a: '2',
373         'progress': lambda *a: 0,
374         'sequence': lambda *a: 10,
375         'active': lambda *a: True,
376         'date_start': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
377         'project_id': _default_project,
378         'occupation_rate':lambda *a: '1',
379         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'project.task', context=c)
380     }
381     _order = "sequence, priority, date_deadline, id"
382
383     #
384     # Override view according to the company definition
385     #
386     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
387         obj_tm = self.pool.get('res.users').browse(cr, uid, uid, context).company_id.project_time_mode_id
388         tm = obj_tm and obj_tm.name or 'Hours'
389
390         res = super(task, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu)
391
392         if tm in ['Hours','Hour']:
393             return res
394
395         eview = etree.fromstring(res['arch'])
396
397         def _check_rec(eview):
398             if eview.attrib.get('widget','') == 'float_time':
399                 eview.set('widget','float')
400             for child in eview:
401                 _check_rec(child)
402             return True
403
404         _check_rec(eview)
405
406         res['arch'] = etree.tostring(eview)
407
408         for f in res['fields']:
409             if 'Hours' in res['fields'][f]['string']:
410                 res['fields'][f]['string'] = res['fields'][f]['string'].replace('Hours',tm)
411
412         return res
413
414     def do_close(self, cr, uid, ids, *args):
415         request = self.pool.get('res.request')
416         tasks = self.browse(cr, uid, ids)
417         for task in tasks:
418             project = task.project_id
419             if project:
420                 if project.warn_manager and project.user_id and (project.user_id.id != uid):
421                     request.create(cr, uid, {
422                         'name': _("Task '%s' closed") % task.name,
423                         'state': 'waiting',
424                         'act_from': uid,
425                         'act_to': project.user_id.id,
426                         'ref_partner_id': task.partner_id.id,
427                         'ref_doc1': 'project.task,%d'% (task.id,),
428                         'ref_doc2': 'project.project,%d'% (project.id,),
429                     })
430             self.write(cr, uid, [task.id], {'state': 'done', 'date_close':time.strftime('%Y-%m-%d %H:%M:%S'), 'remaining_hours': 0.0})
431             if task.parent_ids and task.parent_ids.state in ('pending','draft'):
432                 reopen = True
433                 for child in task.parent_ids.child_ids:
434                     if child.id != task.id and child.state not in ('done','cancelled'):
435                         reopen = False
436                 if reopen:
437                     self.do_reopen(cr, uid, [task.parent_ids.id])
438         return True
439
440     def do_reopen(self, cr, uid, ids, *args):
441         request = self.pool.get('res.request')
442         tasks = self.browse(cr, uid, ids)
443         for task in tasks:
444             project = task.project_id
445             if project and project.warn_manager and project.user_id.id and (project.user_id.id != uid):
446                 request.create(cr, uid, {
447                     'name': _("Task '%s' set in progress") % task.name,
448                     'state': 'waiting',
449                     'act_from': uid,
450                     'act_to': project.user_id.id,
451                     'ref_partner_id': task.partner_id.id,
452                     'ref_doc1': 'project.task,%d' % task.id,
453                     'ref_doc2': 'project.project,%d' % project.id,
454                 })
455
456             self.write(cr, uid, [task.id], {'state': 'open'})
457         return True
458
459     def do_cancel(self, cr, uid, ids, *args):
460         request = self.pool.get('res.request')
461         tasks = self.browse(cr, uid, ids)
462         for task in tasks:
463             project = task.project_id
464             if project.warn_manager and project.user_id and (project.user_id.id != uid):
465                 request.create(cr, uid, {
466                     'name': _("Task '%s' cancelled") % task.name,
467                     'state': 'waiting',
468                     'act_from': uid,
469                     'act_to': project.user_id.id,
470                     'ref_partner_id': task.partner_id.id,
471                     'ref_doc1': 'project.task,%d' % task.id,
472                     'ref_doc2': 'project.project,%d' % project.id,
473                 })
474             self.write(cr, uid, [task.id], {'state': 'cancelled', 'remaining_hours':0.0})
475         return True
476
477     def do_open(self, cr, uid, ids, *args):
478         tasks= self.browse(cr,uid,ids)
479         for t in tasks:
480             self.write(cr, uid, [t.id], {'state': 'open'})
481         return True
482
483     def do_draft(self, cr, uid, ids, *args):
484         self.write(cr, uid, ids, {'state': 'draft'})
485         return True
486
487
488     def do_pending(self, cr, uid, ids, *args):
489         self.write(cr, uid, ids, {'state': 'pending'})
490         return True
491
492     def next_type(self, cr, uid, ids, *args):
493         for typ in self.browse(cr, uid, ids):
494             typeid = typ.type.id
495             types = map(lambda x:x.id, typ.project_id.type_ids or [])
496             if types:
497                 if not typeid:
498                     self.write(cr, uid, typ.id, {'type': types[0]})
499                 elif typeid and typeid in types and types.index(typeid) != len(types)-1 :
500                     index = types.index(typeid)
501                     self.write(cr, uid, typ.id, {'type': types[index+1]})
502         return True
503
504     def prev_type(self, cr, uid, ids, *args):
505         for typ in self.browse(cr, uid, ids):
506             typeid = typ.type.id
507             types = map(lambda x:x.id, typ.project_id.type_ids)
508             if types:
509                 if typeid and typeid in types and types.index(typeid) != 0 :
510                     index = types.index(typeid)
511                     self.write(cr, uid, typ.id, {'type': types[index-1]})
512         return True
513
514 task()
515
516 class project_work(osv.osv):
517     _name = "project.task.work"
518     _description = "Task Work"
519     _columns = {
520         'name': fields.char('Work summary', size=128),
521         'date': fields.datetime('Date'),
522         'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True),
523         'hours': fields.float('Time Spent'),
524         'user_id': fields.many2one('res.users', 'Done by', required=True),
525         'company_id': fields.related('task_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
526     }
527     _defaults = {
528         'user_id': lambda obj,cr,uid,context: uid,
529         'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S')
530     }
531     _order = "date desc"
532     def create(self, cr, uid, vals, *args, **kwargs):
533         if 'hours' in vals and (not vals['hours']):
534             vals['hours'] = 0.00
535         if 'task_id' in vals:
536             cr.execute('update project_task set remaining_hours=remaining_hours - %s where id=%s', (vals.get('hours',0.0), vals['task_id']))
537         return super(project_work,self).create(cr, uid, vals, *args, **kwargs)
538
539     def write(self, cr, uid, ids,vals,context={}):
540         if 'hours' in vals and (not vals['hours']):
541             vals['hours'] = 0.00
542         if 'hours' in vals:
543             for work in self.browse(cr, uid, ids, context):
544                 cr.execute('update project_task set remaining_hours=remaining_hours - %s + (%s) where id=%s', (vals.get('hours',0.0), work.hours, work.task_id.id))
545         return super(project_work,self).write(cr, uid, ids, vals, context)
546
547     def unlink(self, cr, uid, ids, *args, **kwargs):
548         for work in self.browse(cr, uid, ids):
549             cr.execute('update project_task set remaining_hours=remaining_hours + %s where id=%s', (work.hours, work.task_id.id))
550         return super(project_work,self).unlink(cr, uid, ids,*args, **kwargs)
551 project_work()
552
553 class config_compute_remaining(osv.osv_memory):
554     _name='config.compute.remaining'
555     def _get_remaining(self,cr, uid, ctx):
556         if 'active_id' in ctx:
557             return self.pool.get('project.task').browse(cr,uid,ctx['active_id']).remaining_hours
558         return False
559
560     _columns = {
561         'remaining_hours' : fields.float('Remaining Hours', digits=(16,2), help="Total remaining time, can be re-estimated periodically by the assignee of the task."),
562             }
563
564     _defaults = {
565         'remaining_hours': _get_remaining
566         }
567
568     def compute_hours(self, cr, uid, ids, context=None):
569         if 'active_id' in context:
570             remaining_hrs=self.browse(cr,uid,ids)[0].remaining_hours
571             self.pool.get('project.task').write(cr,uid,context['active_id'],{'remaining_hours':remaining_hrs})
572         return {
573                 'type': 'ir.actions.act_window_close',
574          }
575 config_compute_remaining()
576
577 class message(osv.osv):
578     _name = "project.message"
579     _description = "Message"
580     _columns = {
581         'subject': fields.char('Subject', size=128),
582         'description': fields.char('Description', size =128),
583         'project_id': fields.many2one('project.project', 'Project', ondelete='cascade'),
584         'date': fields.date('Date'),
585         'user_id': fields.many2one('res.users', 'User'),
586         }
587 message()
588
589 def _project_get(self, cr, uid, context={}):
590     if uid==1:
591         ids = self.pool.get('project.project').search(cr, uid, [])
592         res = self.pool.get('project.project').read(cr, uid, ids, ['id','name'], context)
593         res = [(str(r['id']),r['name']) for r in res]
594     else:
595         cr.execute("""SELECT project.id,account.name FROM project_project project
596                    LEFT JOIN account_analytic_account account ON account.id = project.category_id
597                    WHERE (account.user_id = %s) OR project.id IN (SELECT project_id FROM project_resource_rel
598                                                                  WHERE resource_id IN (SELECT id FROM resource_resource
599                                                                                        WHERE (user_id= %s)))"""%(uid, uid))
600         res = cr.fetchall()
601         res = [(str(r[0]),r[1]) for r in res]
602     return res
603
604 class users(osv.osv):
605     _inherit = 'res.users'
606     _description = "Users"
607     _columns = {
608         'context_project_id': fields.selection(_project_get, 'Project'),
609         }
610 users()
611
612 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: