[IMP] make changes into the subtype data for project module
[odoo/odoo.git] / addons / project_issue / project_issue.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 base_status.base_stage import base_stage
23 from crm import crm
24 from datetime import datetime
25 from osv import fields,osv
26 from tools.translate import _
27 import binascii
28 import time
29 import tools
30
31 class project_issue_version(osv.osv):
32     _name = "project.issue.version"
33     _order = "name desc"
34     _columns = {
35         'name': fields.char('Version Number', size=32, required=True),
36         'active': fields.boolean('Active', required=False),
37     }
38     _defaults = {
39         'active': 1,
40     }
41 project_issue_version()
42
43 _ISSUE_STATE= [('draft', 'New'), ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Done'),('pending', 'Pending')]
44
45 class project_issue(base_stage, osv.osv):
46     _name = "project.issue"
47     _description = "Project Issue"
48     _order = "priority, create_date desc"
49     _inherit = ['mail.thread', 'ir.needaction_mixin']
50
51     def _get_default_project_id(self, cr, uid, context=None):
52         """ Gives default project by checking if present in the context """
53         return self._resolve_project_id_from_context(cr, uid, context=context)
54
55     def _get_default_stage_id(self, cr, uid, context=None):
56         """ Gives default stage_id """
57         project_id = self._get_default_project_id(cr, uid, context=context)
58         return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
59
60     def _resolve_project_id_from_context(self, cr, uid, context=None):
61         """ Returns ID of project based on the value of 'default_project_id'
62             context key, or None if it cannot be resolved to a single
63             project.
64         """
65         if context is None:
66             context = {}
67         if type(context.get('default_project_id')) in (int, long):
68             return context.get('default_project_id')
69         if isinstance(context.get('default_project_id'), basestring):
70             project_name = context['default_project_id']
71             project_ids = self.pool.get('project.project').name_search(cr, uid, name=project_name, context=context)
72             if len(project_ids) == 1:
73                 return int(project_ids[0][0])
74         return None
75
76     def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
77         access_rights_uid = access_rights_uid or uid
78         stage_obj = self.pool.get('project.task.type')
79         order = stage_obj._order
80         # lame hack to allow reverting search, should just work in the trivial case
81         if read_group_order == 'stage_id desc':
82             order = "%s desc" % order
83         # retrieve section_id from the context and write the domain
84         # - ('id', 'in', 'ids'): add columns that should be present
85         # - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded
86         # - OR ('project_ids', 'in', project_id), ('fold', '=', False) if project_id: add project columns that are not folded
87         search_domain = []
88         project_id = self._resolve_project_id_from_context(cr, uid, context=context)
89         if project_id:
90             search_domain += ['|', '&', ('project_ids', '=', project_id), ('fold', '=', False)]
91         search_domain += ['|', ('id', 'in', ids), '&', ('case_default', '=', True), ('fold', '=', False)]
92         # perform search
93         stage_ids = stage_obj._search(cr, uid, search_domain, order=order, access_rights_uid=access_rights_uid, context=context)
94         result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
95         # restore order of the search
96         result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
97         return result
98
99     def _compute_day(self, cr, uid, ids, fields, args, context=None):
100         """
101         @param cr: the current row, from the database cursor,
102         @param uid: the current user’s ID for security checks,
103         @param ids: List of Openday’s IDs
104         @return: difference between current date and log date
105         @param context: A standard dictionary for contextual values
106         """
107         cal_obj = self.pool.get('resource.calendar')
108         res_obj = self.pool.get('resource.resource')
109
110         res = {}
111         for issue in self.browse(cr, uid, ids, context=context):
112             res[issue.id] = {}
113             for field in fields:
114                 duration = 0
115                 ans = False
116                 hours = 0
117
118                 date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
119                 if field in ['working_hours_open','day_open']:
120                     if issue.date_open:
121                         date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
122                         ans = date_open - date_create
123                         date_until = issue.date_open
124                         #Calculating no. of working hours to open the issue
125                         if issue.project_id.resource_calendar_id:
126                             hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
127                                                            date_create,
128                                                            date_open)
129                 elif field in ['working_hours_close','day_close']:
130                     if issue.date_closed:
131                         date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
132                         date_until = issue.date_closed
133                         ans = date_close - date_create
134                         #Calculating no. of working hours to close the issue
135                         if issue.project_id.resource_calendar_id:
136                             hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
137                                date_create,
138                                date_close)
139                 elif field in ['days_since_creation']:
140                     if issue.create_date:
141                         days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
142                         res[issue.id][field] = days_since_creation.days
143                     continue
144
145                 elif field in ['inactivity_days']:
146                     res[issue.id][field] = 0
147                     if issue.date_action_last:
148                         inactive_days = datetime.today() - datetime.strptime(issue.date_action_last, '%Y-%m-%d %H:%M:%S')
149                         res[issue.id][field] = inactive_days.days
150                     continue
151                 if ans:
152                     resource_id = False
153                     if issue.user_id:
154                         resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
155                         if resource_ids and len(resource_ids):
156                             resource_id = resource_ids[0]
157                     duration = float(ans.days)
158                     if issue.project_id and issue.project_id.resource_calendar_id:
159                         duration = float(ans.days) * 24
160
161                         new_dates = cal_obj.interval_min_get(cr, uid,
162                                                              issue.project_id.resource_calendar_id.id,
163                                                              date_create,
164                                                              duration, resource=resource_id)
165                         no_days = []
166                         date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
167                         for in_time, out_time in new_dates:
168                             if in_time.date not in no_days:
169                                 no_days.append(in_time.date)
170                             if out_time > date_until:
171                                 break
172                         duration = len(no_days)
173
174                 if field in ['working_hours_open','working_hours_close']:
175                     res[issue.id][field] = hours
176                 else:
177                     res[issue.id][field] = abs(float(duration))
178
179         return res
180
181     def _hours_get(self, cr, uid, ids, field_names, args, context=None):
182         task_pool = self.pool.get('project.task')
183         res = {}
184         for issue in self.browse(cr, uid, ids, context=context):
185             progress = 0.0
186             if issue.task_id:
187                 progress = task_pool._hours_get(cr, uid, [issue.task_id.id], field_names, args, context=context)[issue.task_id.id]['progress']
188             res[issue.id] = {'progress' : progress}
189         return res
190
191     def on_change_project(self, cr, uid, ids, project_id, context=None):
192         return {}
193
194     def _get_issue_task(self, cr, uid, ids, context=None):
195         issues = []
196         issue_pool = self.pool.get('project.issue')
197         for task in self.pool.get('project.task').browse(cr, uid, ids, context=context):
198             issues += issue_pool.search(cr, uid, [('task_id','=',task.id)])
199         return issues
200
201     def _get_issue_work(self, cr, uid, ids, context=None):
202         issues = []
203         issue_pool = self.pool.get('project.issue')
204         for work in self.pool.get('project.task.work').browse(cr, uid, ids, context=context):
205             if work.task_id:
206                 issues += issue_pool.search(cr, uid, [('task_id','=',work.task_id.id)])
207         return issues
208
209     _columns = {
210         'id': fields.integer('ID', readonly=True),
211         'name': fields.char('Issue', size=128, required=True),
212         'active': fields.boolean('Active', required=False),
213         'create_date': fields.datetime('Creation Date', readonly=True,select=True),
214         'write_date': fields.datetime('Update Date', readonly=True),
215         'days_since_creation': fields.function(_compute_day, string='Days since creation date', \
216                                                multi='compute_day', type="integer", help="Difference in days between creation date and current date"),
217         'date_deadline': fields.date('Deadline'),
218         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
219                         select=True, help='Sales team to which Case belongs to.\
220                              Define Responsible user and Email account for mail gateway.'),
221         'partner_id': fields.many2one('res.partner', 'Contact', select=1),
222         'company_id': fields.many2one('res.company', 'Company'),
223         'description': fields.text('Description'),
224         'state': fields.related('stage_id', 'state', type="selection", store=True,
225                 selection=_ISSUE_STATE, string="State", readonly=True,
226                 help='The state is set to \'Draft\', when a case is created.\
227                       If the case is in progress the state is set to \'Open\'.\
228                       When the case is over, the state is set to \'Done\'.\
229                       If the case needs to be reviewed then the state is \
230                       set to \'Pending\'.'),
231         'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1),
232         'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
233         'date_open': fields.datetime('Opened', readonly=True,select=True),
234         # Project Issue fields
235         'date_closed': fields.datetime('Closed', readonly=True,select=True),
236         'date': fields.datetime('Date'),
237         'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."),
238         'categ_ids': fields.many2many('project.category', string='Categories'),
239         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
240         'version_id': fields.many2one('project.issue.version', 'Version'),
241         'stage_id': fields.many2one ('project.task.type', 'Stage',
242                         domain="['|', ('project_ids', '=', project_id), ('case_default', '=', True)]"),
243         'project_id':fields.many2one('project.project', 'Project'),
244         'duration': fields.float('Duration'),
245         'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
246         'day_open': fields.function(_compute_day, string='Days to Open', \
247                                 multi='compute_day', type="float", store=True),
248         'day_close': fields.function(_compute_day, string='Days to Close', \
249                                 multi='compute_day', type="float", store=True),
250         'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1),
251         'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
252                                 multi='compute_day', type="float", store=True),
253         'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
254                                 multi='compute_day', type="float", store=True),
255         'inactivity_days': fields.function(_compute_day, string='Days since last action', \
256                                 multi='compute_day', type="integer", help="Difference in days between last action and current date"),
257         'color': fields.integer('Color Index'),
258         'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
259         'date_action_last': fields.datetime('Last Action', readonly=1),
260         'date_action_next': fields.datetime('Next Action', readonly=1),
261         'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="Computed as: Time Spent / Total Time.",
262             store = {
263                 'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['task_id'], 10),
264                 'project.task': (_get_issue_task, ['progress'], 10),
265                 'project.task.work': (_get_issue_work, ['hours'], 10),
266             }),
267     }
268
269     _defaults = {
270         'active': 1,
271         'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c),
272         'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
273         'state': 'draft',
274         'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
275         'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
276         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
277         'priority': crm.AVAILABLE_PRIORITIES[2][0],
278          }
279
280     _group_by_full = {
281         'stage_id': _read_group_stage_ids
282     }
283
284     def set_priority(self, cr, uid, ids, priority, *args):
285         """Set lead priority
286         """
287         return self.write(cr, uid, ids, {'priority' : priority})
288
289     def set_high_priority(self, cr, uid, ids, *args):
290         """Set lead priority to high
291         """
292         return self.set_priority(cr, uid, ids, '1')
293
294     def set_normal_priority(self, cr, uid, ids, *args):
295         """Set lead priority to normal
296         """
297         return self.set_priority(cr, uid, ids, '3')
298
299     def convert_issue_task(self, cr, uid, ids, context=None):
300         if context is None:
301             context = {}
302
303         case_obj = self.pool.get('project.issue')
304         data_obj = self.pool.get('ir.model.data')
305         task_obj = self.pool.get('project.task')
306
307         result = data_obj._get_id(cr, uid, 'project', 'view_task_search_form')
308         res = data_obj.read(cr, uid, result, ['res_id'])
309         id2 = data_obj._get_id(cr, uid, 'project', 'view_task_form2')
310         id3 = data_obj._get_id(cr, uid, 'project', 'view_task_tree2')
311         if id2:
312             id2 = data_obj.browse(cr, uid, id2, context=context).res_id
313         if id3:
314             id3 = data_obj.browse(cr, uid, id3, context=context).res_id
315
316         for bug in case_obj.browse(cr, uid, ids, context=context):
317             new_task_id = task_obj.create(cr, uid, {
318                 'name': bug.name,
319                 'partner_id': bug.partner_id.id,
320                 'description':bug.description,
321                 'date_deadline': bug.date,
322                 'project_id': bug.project_id.id,
323                 # priority must be in ['0','1','2','3','4'], while bug.priority is in ['1','2','3','4','5']
324                 'priority': str(int(bug.priority) - 1),
325                 'user_id': bug.user_id.id,
326                 'planned_hours': 0.0,
327             })
328             vals = {
329                 'task_id': new_task_id,
330                 'state':'pending'
331             }
332             self.convert_to_task_send_note(cr, uid, [bug.id], context=context)
333             case_obj.write(cr, uid, [bug.id], vals, context=context)
334             self.case_pending_send_note(cr, uid, [bug.id], context=context)
335
336         return  {
337             'name': _('Tasks'),
338             'view_type': 'form',
339             'view_mode': 'form,tree',
340             'res_model': 'project.task',
341             'res_id': int(new_task_id),
342             'view_id': False,
343             'views': [(id2,'form'),(id3,'tree'),(False,'calendar'),(False,'graph')],
344             'type': 'ir.actions.act_window',
345             'search_view_id': res['res_id'],
346             'nodestroy': True
347         }
348
349     def copy(self, cr, uid, id, default=None, context=None):
350         issue = self.read(cr, uid, id, ['name'], context=context)
351         if not default:
352             default = {}
353         default = default.copy()
354         default['name'] = issue['name'] + _(' (copy)')
355         return super(project_issue, self).copy(cr, uid, id, default=default,
356                 context=context)
357
358     def write(self, cr, uid, ids, vals, context=None):
359         #Update last action date every time the user change the stage, the state or send a new email
360         logged_fields = ['stage_id', 'state', 'message_ids']
361         if any([field in vals for field in logged_fields]):
362             vals['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S')
363         return super(project_issue, self).write(cr, uid, ids, vals, context)
364
365     def onchange_task_id(self, cr, uid, ids, task_id, context=None):
366         result = {}
367         if not task_id:
368             return {'value':{}}
369         task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
370         return {'value':{'user_id': task.user_id.id,}}
371
372     def case_reset(self, cr, uid, ids, context=None):
373         """Resets case as draft
374         """
375         res = super(project_issue, self).case_reset(cr, uid, ids, context)
376         self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
377         return res
378
379     def create(self, cr, uid, vals, context=None):
380         obj_id = super(project_issue, self).create(cr, uid, vals, context=context)
381         self.create_send_note(cr, uid, [obj_id], context=context)
382         return obj_id
383
384     # -------------------------------------------------------
385     # Stage management
386     # -------------------------------------------------------
387
388     def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
389         """ Override of the base.stage method
390             Parameter of the stage search taken from the issue:
391             - type: stage type must be the same or 'both'
392             - section_id: if set, stages must belong to this section or
393               be a default case
394         """
395         if isinstance(cases, (int, long)):
396             cases = self.browse(cr, uid, cases, context=context)
397         # collect all section_ids
398         section_ids = []
399         if section_id:
400             section_ids.append(section_id)
401         for task in cases:
402             if task.project_id:
403                 section_ids.append(task.project_id.id)
404         # OR all section_ids and OR with case_default
405         search_domain = []
406         if section_ids:
407             search_domain += [('|')] * len(section_ids)
408             for section_id in section_ids:
409                 search_domain.append(('project_ids', '=', section_id))
410         search_domain.append(('case_default', '=', True))
411         # AND with the domain in parameter
412         search_domain += list(domain)
413         # perform search, return the first found
414         stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context)
415         if stage_ids:
416             return stage_ids[0]
417         return False
418
419     def case_cancel(self, cr, uid, ids, context=None):
420         """ Cancels case """
421         self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context)
422         self.case_cancel_send_note(cr, uid, ids, context=context)
423         return True
424
425     def case_escalate(self, cr, uid, ids, context=None):
426         cases = self.browse(cr, uid, ids)
427         for case in cases:
428             data = {}
429             if case.project_id.project_escalation_id:
430                 data['project_id'] = case.project_id.project_escalation_id.id
431                 if case.project_id.project_escalation_id.user_id:
432                     data['user_id'] = case.project_id.project_escalation_id.user_id.id
433                 if case.task_id:
434                     self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False})
435             else:
436                 raise osv.except_osv(_('Warning!'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
437             self.case_set(cr, uid, ids, 'draft', data, context=context)
438             self.case_escalate_send_note(cr, uid, [case.id], context=context)
439         return True
440
441     # -------------------------------------------------------
442     # Mail gateway
443     # -------------------------------------------------------
444
445     def message_new(self, cr, uid, msg, custom_values=None, context=None):
446         """ Overrides mail_thread message_new that is called by the mailgateway
447             through message_process.
448             This override updates the document according to the email.
449         """
450         if custom_values is None: custom_values = {}
451         if context is None: context = {}
452         context['state_to'] = 'draft'
453
454         custom_values.update({
455             'name':  msg.get('subject') or _("No Subject"),
456             'description': msg.get('body'),
457             'email_from': msg.get('from'),
458             'email_cc': msg.get('cc'),
459             'user_id': False,
460         })
461         if  msg.get('priority'):
462             custom_values['priority'] =  msg.get('priority')
463
464         res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
465         # self.convert_to_bug(cr, uid, [res_id], context=context)
466         return res_id
467
468     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
469         """ Overrides mail_thread message_update that is called by the mailgateway
470             through message_process.
471             This method updates the document according to the email.
472         """
473         if isinstance(ids, (str, int, long)):
474             ids = [ids]
475         if update_vals is None: update_vals = {}
476
477         # Update doc values according to the message
478         update_vals['description'] = msg.get('body', '')
479         if msg.get('priority'):
480             update_vals['priority'] = msg.get('priority')
481         # Parse 'body' to find values to update
482         maps = {
483             'cost': 'planned_cost',
484             'revenue': 'planned_revenue',
485             'probability': 'probability',
486         }
487         for line in msg.get('body', '').split('\n'):
488             line = line.strip()
489             res = tools.misc.command_re.match(line)
490             if res and maps.get(res.group(1).lower(), False):
491                 key = maps.get(res.group(1).lower())
492                 update_vals[key] = res.group(2).lower()
493
494         return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
495
496     # -------------------------------------------------------
497     # OpenChatter methods and notifications
498     # -------------------------------------------------------
499
500     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
501         """ Override of the (void) default notification method. """
502         stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
503         return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), subtype="stage change", context=context)
504
505     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
506         """ Override of default prefix for notifications. """
507         return 'Project issue'
508
509     def convert_to_task_send_note(self, cr, uid, ids, context=None):
510         message = _("Project issue <b>converted</b> to task.")
511         return self.message_post(cr, uid, ids, body=message, context=context)
512
513     def create_send_note(self, cr, uid, ids, context=None):
514         message = _("Project issue <b>created</b>.")
515         return self.message_post(cr, uid, ids, body=message, subtype="new", context=context)
516
517     def case_escalate_send_note(self, cr, uid, ids, context=None):
518         for obj in self.browse(cr, uid, ids, context=context):
519             if obj.project_id:
520                 message = _("<b>escalated</b> to <em>'%s'</em>.") % (obj.project_id.name)
521                 obj.message_post(body=message)
522             else:
523                 message = _("<b>escalated</b>.")
524                 obj.message_post(body=message)
525         return True
526
527 project_issue()
528
529 class project(osv.osv):
530     _inherit = "project.project"
531
532     def _get_alias_models(self, cr, uid, context=None):
533         return [('project.task', "Tasks"), ("project.issue", "Issues")]
534         
535     def _issue_count(self, cr, uid, ids, field_name, arg, context=None):
536         res = dict.fromkeys(ids, 0)
537         issue_ids = self.pool.get('project.issue').search(cr, uid, [('project_id', 'in', ids)])
538         for issue in self.pool.get('project.issue').browse(cr, uid, issue_ids, context):
539             res[issue.project_id.id] += 1
540         return res
541
542     _columns = {
543         'project_escalation_id' : fields.many2one('project.project','Project Escalation', help='If any issue is escalated from the current Project, it will be listed under the project selected here.', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
544         'issue_count': fields.function(_issue_count, type='integer'),
545     }
546
547     def _check_escalation(self, cr, uid, ids, context=None):
548         project_obj = self.browse(cr, uid, ids[0], context=context)
549         if project_obj.project_escalation_id:
550             if project_obj.project_escalation_id.id == project_obj.id:
551                 return False
552         return True
553
554     _constraints = [
555         (_check_escalation, 'Error! You cannot assign escalation to the same project!', ['project_escalation_id'])
556     ]
557     
558 project()
559
560 class account_analytic_account(osv.osv):
561     _inherit = 'account.analytic.account'
562     _description = 'Analytic Account'
563
564     _columns = {
565         'use_issues' : fields.boolean('Issues', help="Check this field if this project manages issues"),
566     }
567
568     def on_change_template(self, cr, uid, ids, template_id, context=None):
569         res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
570         if template_id and 'value' in res:
571             template = self.browse(cr, uid, template_id, context=context)
572             res['value']['use_issues'] = template.use_issues
573         return res
574
575     def _trigger_project_creation(self, cr, uid, vals, context=None):
576         if context is None: context = {}
577         res = super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
578         return res or (vals.get('use_issues') and not 'project_creation_in_progress' in context)
579
580 account_analytic_account()
581
582 class project_project(osv.osv):
583     _inherit = 'project.project'
584     _defaults = {
585         'use_issues': True
586     }
587
588 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: