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