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