[FIX]: Fix id and None project_id problem
[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         project_obj = self.pool.get("project.project")
385         project_id = self.browse(cr, uid, obj_id, context=context).project_id
386         if project_id:
387             followers = [follower.id for follower in project_id.message_follower_ids]
388             self.message_subscribe(cr, uid, [obj_id], followers, context=context)
389         self.create_send_note(cr, uid, [obj_id], context=context)
390         return obj_id
391
392     # -------------------------------------------------------
393     # Stage management
394     # -------------------------------------------------------
395
396     def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
397         """ Override of the base.stage method
398             Parameter of the stage search taken from the issue:
399             - type: stage type must be the same or 'both'
400             - section_id: if set, stages must belong to this section or
401               be a default case
402         """
403         if isinstance(cases, (int, long)):
404             cases = self.browse(cr, uid, cases, context=context)
405         # collect all section_ids
406         section_ids = []
407         if section_id:
408             section_ids.append(section_id)
409         for task in cases:
410             if task.project_id:
411                 section_ids.append(task.project_id.id)
412         # OR all section_ids and OR with case_default
413         search_domain = []
414         if section_ids:
415             search_domain += [('|')] * len(section_ids)
416             for section_id in section_ids:
417                 search_domain.append(('project_ids', '=', section_id))
418         search_domain.append(('case_default', '=', True))
419         # AND with the domain in parameter
420         search_domain += list(domain)
421         # perform search, return the first found
422         stage_ids = self.pool.get('project.task.type').search(cr, uid, search_domain, order=order, context=context)
423         if stage_ids:
424             return stage_ids[0]
425         return False
426
427     def case_cancel(self, cr, uid, ids, context=None):
428         """ Cancels case """
429         self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context)
430         self.case_cancel_send_note(cr, uid, ids, context=context)
431         return True
432
433     def case_escalate(self, cr, uid, ids, context=None):
434         cases = self.browse(cr, uid, ids)
435         for case in cases:
436             data = {}
437             if case.project_id.project_escalation_id:
438                 data['project_id'] = case.project_id.project_escalation_id.id
439                 if case.project_id.project_escalation_id.user_id:
440                     data['user_id'] = case.project_id.project_escalation_id.user_id.id
441                 if case.task_id:
442                     self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False})
443             else:
444                 raise osv.except_osv(_('Warning!'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
445             self.case_set(cr, uid, ids, 'draft', data, context=context)
446             self.case_escalate_send_note(cr, uid, [case.id], context=context)
447         return True
448
449     # -------------------------------------------------------
450     # Mail gateway
451     # -------------------------------------------------------
452
453     def message_new(self, cr, uid, msg, custom_values=None, context=None):
454         """ Overrides mail_thread message_new that is called by the mailgateway
455             through message_process.
456             This override updates the document according to the email.
457         """
458         if custom_values is None: custom_values = {}
459         if context is None: context = {}
460         context['state_to'] = 'draft'
461
462         custom_values.update({
463             'name':  msg.get('subject') or _("No Subject"),
464             'description': msg.get('body'),
465             'email_from': msg.get('from'),
466             'email_cc': msg.get('cc'),
467             'user_id': False,
468         })
469         if  msg.get('priority'):
470             custom_values['priority'] =  msg.get('priority')
471
472         res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
473         # self.convert_to_bug(cr, uid, [res_id], context=context)
474         return res_id
475
476     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
477         """ Overrides mail_thread message_update that is called by the mailgateway
478             through message_process.
479             This method updates the document according to the email.
480         """
481         if isinstance(ids, (str, int, long)):
482             ids = [ids]
483         if update_vals is None: update_vals = {}
484
485         # Update doc values according to the message
486         update_vals['description'] = msg.get('body', '')
487         if msg.get('priority'):
488             update_vals['priority'] = msg.get('priority')
489         # Parse 'body' to find values to update
490         maps = {
491             'cost': 'planned_cost',
492             'revenue': 'planned_revenue',
493             'probability': 'probability',
494         }
495         for line in msg.get('body', '').split('\n'):
496             line = line.strip()
497             res = tools.misc.command_re.match(line)
498             if res and maps.get(res.group(1).lower(), False):
499                 key = maps.get(res.group(1).lower())
500                 update_vals[key] = res.group(2).lower()
501
502         return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
503
504     # -------------------------------------------------------
505     # OpenChatter methods and notifications
506     # -------------------------------------------------------
507
508     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
509         """ Override of the (void) default notification method. """
510         stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
511         return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
512
513     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
514         """ Override of default prefix for notifications. """
515         return 'Project issue'
516
517     def convert_to_task_send_note(self, cr, uid, ids, context=None):
518         message = _("Project issue <b>converted</b> to task.")
519         return self.message_post(cr, uid, ids, body=message, context=context)
520
521     def create_send_note(self, cr, uid, ids, context=None):
522         message = _("Project issue <b>created</b>.")
523         return self.message_post(cr, uid, ids, body=message, context=context)
524
525     def case_escalate_send_note(self, cr, uid, ids, context=None):
526         for obj in self.browse(cr, uid, ids, context=context):
527             if obj.project_id:
528                 message = _("<b>escalated</b> to <em>'%s'</em>.") % (obj.project_id.name)
529                 obj.message_post(body=message)
530             else:
531                 message = _("<b>escalated</b>.")
532                 obj.message_post(body=message)
533         return True
534
535 project_issue()
536
537 class project(osv.osv):
538     _inherit = "project.project"
539
540     def _get_alias_models(self, cr, uid, context=None):
541         return [('project.task', "Tasks"), ("project.issue", "Issues")]
542         
543     def _issue_count(self, cr, uid, ids, field_name, arg, context=None):
544         res = dict.fromkeys(ids, 0)
545         issue_ids = self.pool.get('project.issue').search(cr, uid, [('project_id', 'in', ids)])
546         for issue in self.pool.get('project.issue').browse(cr, uid, issue_ids, context):
547             res[issue.project_id.id] += 1
548         return res
549
550     _columns = {
551         '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)]}),
552         'issue_count': fields.function(_issue_count, type='integer'),
553     }
554
555     def _check_escalation(self, cr, uid, ids, context=None):
556         project_obj = self.browse(cr, uid, ids[0], context=context)
557         if project_obj.project_escalation_id:
558             if project_obj.project_escalation_id.id == project_obj.id:
559                 return False
560         return True
561
562     _constraints = [
563         (_check_escalation, 'Error! You cannot assign escalation to the same project!', ['project_escalation_id'])
564     ]
565     
566 project()
567
568 class account_analytic_account(osv.osv):
569     _inherit = 'account.analytic.account'
570     _description = 'Analytic Account'
571
572     _columns = {
573         'use_issues' : fields.boolean('Issues', help="Check this field if this project manages issues"),
574     }
575
576     def on_change_template(self, cr, uid, ids, template_id, context=None):
577         res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
578         if template_id and 'value' in res:
579             template = self.browse(cr, uid, template_id, context=context)
580             res['value']['use_issues'] = template.use_issues
581         return res
582
583     def _trigger_project_creation(self, cr, uid, vals, context=None):
584         if context is None: context = {}
585         res = super(account_analytic_account, self)._trigger_project_creation(cr, uid, vals, context=context)
586         return res or (vals.get('use_issues') and not 'project_creation_in_progress' in context)
587
588 account_analytic_account()
589
590 class project_project(osv.osv):
591     _inherit = 'project.project'
592     _defaults = {
593         'use_issues': True
594     }
595
596 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: