[IMP]: bugfixing and code cleaning in project and project_* modules
[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 import base64
23 import os
24 import re
25 import time
26 import mx.DateTime
27 from datetime import datetime, timedelta
28 import binascii
29 import collections
30
31 import tools
32 from crm import crm
33 from osv import fields,osv,orm
34 from osv.orm import except_orm
35 from tools.translate import _
36 import tools
37
38 class project_issue(osv.osv, crm.crm_case):
39     _name = "project.issue"
40     _description = "Project Issue"
41     _order = "priority, id desc"
42     _inherit = ['mailgate.thread']
43
44     def case_open(self, cr, uid, ids, *args):
45         """
46         @param self: The object pointer
47         @param cr: the current row, from the database cursor,
48         @param uid: the current user’s ID for security checks,
49         @param ids: List of case's Ids
50         @param *args: Give Tuple Value
51         """
52
53         res = super(project_issue, self).case_open(cr, uid, ids, *args)
54         self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
55         for (id, name) in self.name_get(cr, uid, ids):
56             message = _('Issue ') + " '" + name + "' "+ _("is Open.")
57             self.log(cr, uid, id, message)
58         return res
59
60     def case_close(self, cr, uid, ids, *args):
61         """
62         @param self: The object pointer
63         @param cr: the current row, from the database cursor,
64         @param uid: the current user’s ID for security checks,
65         @param ids: List of case's Ids
66         @param *args: Give Tuple Value
67         """
68
69         res = super(project_issue, self).case_close(cr, uid, ids, *args)
70         for (id, name) in self.name_get(cr, uid, ids):
71             message = _('Issue ') + " '" + name + "' "+ _("is Closed.")
72             self.log(cr, uid, id, message)
73         return res
74     
75     def _compute_day(self, cr, uid, ids, fields, args, context=None):
76         if context is None:
77             context = {}
78         """
79         @param cr: the current row, from the database cursor,
80         @param uid: the current user’s ID for security checks,
81         @param ids: List of Openday’s IDs
82         @return: difference between current date and log date
83         @param context: A standard dictionary for contextual values
84         """
85         cal_obj = self.pool.get('resource.calendar')
86         res_obj = self.pool.get('resource.resource')
87
88         res = {}
89         for issue in self.browse(cr, uid, ids, context=context):
90             for field in fields:
91                 res[issue.id] = {}
92                 duration = 0
93                 ans = False
94                 hours = 0
95
96                 if field in ['working_hours_open','day_open']:
97                     if issue.date_open:
98                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
99                         date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
100                         ans = date_open - date_create
101                         date_until = issue.date_open
102                         #Calculating no. of working hours to open the issue
103                         hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
104                                 mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
105                                 mx.DateTime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S'))
106                 elif field in ['working_hours_close','day_close']:
107                     if issue.date_closed:
108                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
109                         date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
110                         date_until = issue.date_closed
111                         ans = date_close - date_create
112                         #Calculating no. of working hours to close the issue
113                         hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
114                                 mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
115                                 mx.DateTime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S'))
116                 if ans:
117                     resource_id = False
118                     if issue.user_id:
119                         resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
120                         if resource_ids and len(resource_ids):
121                             resource_id = resource_ids[0]
122                     duration = float(ans.days)
123                     if issue.project_id and issue.project_id.resource_calendar_id:
124                         duration = float(ans.days) * 24
125                         new_dates = cal_obj.interval_min_get(cr, uid, issue.project_id.resource_calendar_id.id, mx.DateTime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), duration, resource=resource_id)
126                         no_days = []
127                         date_until = mx.DateTime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
128                         for in_time, out_time in new_dates:
129                             if in_time.date not in no_days:
130                                 no_days.append(in_time.date)
131                             if out_time > date_until:
132                                 break
133                         duration = len(no_days)
134                 if field in ['working_hours_open','working_hours_close']:
135                     res[issue.id][field] = hours
136                 else:
137                     res[issue.id][field] = abs(float(duration))
138         return res
139
140     _columns = {
141         'id': fields.integer('ID'),
142         'name': fields.char('Name', size=128, required=True),
143         'active': fields.boolean('Active', required=False),
144         'create_date': fields.datetime('Creation Date', readonly=True),
145         'write_date': fields.datetime('Update Date', readonly=True),
146         'date_deadline': fields.date('Deadline'),
147         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
148                         select=True, help='Sales team to which Case belongs to.\
149                              Define Responsible user and Email account for mail gateway.'),
150         'user_id': fields.many2one('res.users', 'Responsible'),
151         'partner_id': fields.many2one('res.partner', 'Partner'),
152         'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
153                                  domain="[('partner_id','=',partner_id)]"),
154         'company_id': fields.many2one('res.company', 'Company'),
155         'description': fields.text('Description'),
156         'state': fields.selection([('draft', 'Draft'), ('open', 'Todo'), ('cancel', 'Cancelled'), ('done', 'Closed'),('pending', 'Pending'), ], 'State', size=16, readonly=True,
157                                   help='The state is set to \'Draft\', when a case is created.\
158                                   \nIf the case is in progress the state is set to \'Open\'.\
159                                   \nWhen the case is over, the state is set to \'Done\'.\
160                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
161         'email_from': fields.char('Email', size=128, help="These people will receive email."),
162         'email_cc': fields.text('Watchers Emails', size=252, help="These people\
163  will receive a copy of the future" \
164 " communication between partner and users by email"),
165         'date_open': fields.datetime('Opened', readonly=True),
166         # Project Issue fields
167         'date_closed': fields.datetime('Closed', readonly=True),
168         'date': fields.datetime('Date'),
169         'canal_id': fields.many2one('res.partner.canal', 'Channel', help="The channels represent the different communication modes available with the customer." \
170                                                                         " With each commercial opportunity, you can indicate the canall which is this opportunity source."),
171         'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('object_id.model', '=', 'crm.project.bug')]"),
172         'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Severity'),
173         'type_id': fields.many2one('crm.case.resource.type', 'Version', domain="[('object_id.model', '=', 'project.issue')]"),
174         'partner_name': fields.char("Employee's Name", size=64),
175         'partner_mobile': fields.char('Mobile', size=32),
176         'partner_phone': fields.char('Phone', size=32),
177         'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('object_id.model', '=', 'project.issue')]"),
178         'project_id':fields.many2one('project.project', 'Project'),
179         'duration': fields.float('Duration'),
180         'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
181         'date_open': fields.datetime('Opened', readonly=True),
182         'day_open': fields.function(_compute_day, string='Days to Open', \
183                                 method=True, multi='day_open', type="float", store=True),
184         'day_close': fields.function(_compute_day, string='Days to Close', \
185                                 method=True, multi='day_close', type="float", store=True),
186         'assigned_to': fields.many2one('res.users', 'Assigned to'),
187         'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
188                                 method=True, multi='working_days_open', type="float", store=True),
189         'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
190                                 method=True, multi='working_days_close', type="float", store=True),
191         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('history', '=', True),('model','=',_name)]),
192         'log_ids': fields.one2many('mailgate.message', 'res_id', 'Logs', domain=[('history', '=', False),('model','=',_name)]),
193         'date_action_last': fields.datetime('Last Action', readonly=1),
194         'date_action_next': fields.datetime('Next Action', readonly=1),
195     }
196
197     def _get_project(self, cr, uid, context):
198         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
199         if user.context_project_id:
200             return user.context_project_id.id
201         return False
202
203     _defaults = {
204         'active': 1,
205         'user_id': crm.crm_case._get_default_user,
206         'partner_id': crm.crm_case._get_default_partner,
207         'partner_address_id': crm.crm_case._get_default_partner_address,
208         'email_from': crm.crm_case. _get_default_email,
209         'state': 'draft',
210         'section_id': crm.crm_case. _get_section,
211         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
212         'priority': crm.AVAILABLE_PRIORITIES[2][0],
213         'project_id':_get_project,
214     }
215
216     def convert_issue_task(self, cr, uid, ids, context=None):
217         case_obj = self.pool.get('project.issue')
218         data_obj = self.pool.get('ir.model.data')
219         task_obj = self.pool.get('project.task')
220
221         if context is None:
222             context = {}
223
224         result = data_obj._get_id(cr, uid, 'project', 'view_task_search_form')
225         res = data_obj.read(cr, uid, result, ['res_id'])
226         id2 = data_obj._get_id(cr, uid, 'project', 'view_task_form2')
227         id3 = data_obj._get_id(cr, uid, 'project', 'view_task_tree2')
228         if id2:
229             id2 = data_obj.browse(cr, uid, id2, context=context).res_id
230         if id3:
231             id3 = data_obj.browse(cr, uid, id3, context=context).res_id
232
233         for bug in case_obj.browse(cr, uid, ids, context=context):
234             new_task_id = task_obj.create(cr, uid, {
235                 'name': bug.name,
236                 'partner_id': bug.partner_id.id,
237                 'description':bug.description,
238                 'date': bug.date,
239                 'project_id': bug.project_id.id,
240                 'priority': bug.priority,
241                 'user_id': bug.assigned_to.id,
242                 'planned_hours': 0.0,
243             })
244
245             vals = {
246                 'task_id': new_task_id,
247             }
248             case_obj.write(cr, uid, [bug.id], vals)
249
250         return  {
251             'name': _('Tasks'),
252             'view_type': 'form',
253             'view_mode': 'form,tree',
254             'res_model': 'project.task',
255             'res_id': int(new_task_id),
256             'view_id': False,
257             'views': [(id2,'form'),(id3,'tree'),(False,'calendar'),(False,'graph')],
258             'type': 'ir.actions.act_window',
259             'search_view_id': res['res_id'],
260             'nodestroy': True
261         }
262
263     def _convert(self, cr, uid, ids, xml_id, context=None):
264         data_obj = self.pool.get('ir.model.data')
265         id2 = data_obj._get_id(cr, uid, 'project_issue', xml_id)
266         categ_id = False
267         if id2:
268             categ_id = data_obj.browse(cr, uid, id2, context=context).res_id
269         if categ_id:
270             self.write(cr, uid, ids, {'categ_id': categ_id})
271         return True
272
273     def convert_to_feature(self, cr, uid, ids, context=None):
274         return self._convert(cr, uid, ids, 'feature_request_categ', context=context)
275
276     def convert_to_bug(self, cr, uid, ids, context=None):
277         return self._convert(cr, uid, ids, 'bug_categ', context=context)
278
279     def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
280         if context is None:
281             context = {}
282         if not stage_id:
283             return {'value':{}}
284         stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
285         if not stage.on_change:
286             return {'value':{}}
287         return {'value':{}}
288
289     def case_escalate(self, cr, uid, ids, *args):
290         """Escalates case to top level
291         @param self: The object pointer
292         @param cr: the current row, from the database cursor,
293         @param uid: the current user’s ID for security checks,
294         @param ids: List of case Ids
295         @param *args: Tuple Value for additional Params
296         """
297         cases = self.browse(cr, uid, ids)
298         for case in cases:
299             data = {}
300             if case.project_id.project_escalation_id:
301                 data['project_id'] = case.project_id.project_escalation_id.id
302                 if case.project_id.project_escalation_id.user_id:
303                     data['user_id'] = case.project_id.project_escalation_id.user_id.id
304             else:
305                 raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
306             self.write(cr, uid, [case.id], data)
307         return True
308
309     def message_new(self, cr, uid, msg, context):
310         """
311         Automatically calls when new email message arrives
312
313         @param self: The object pointer
314         @param cr: the current row, from the database cursor,
315         @param uid: the current user’s ID for security checks
316         """
317
318         mailgate_pool = self.pool.get('email.server.tools')
319
320         subject = msg.get('subject')
321         body = msg.get('body')
322         msg_from = msg.get('from')
323         priority = msg.get('priority')
324
325         vals = {
326             'name': subject,
327             'email_from': msg_from,
328             'email_cc': msg.get('cc'),
329             'description': body,
330             'user_id': False,
331         }
332         if msg.get('priority', False):
333             vals['priority'] = priority
334
335         res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
336         if res:
337             vals.update(res)
338         res = self.create(cr, uid, vals, context)
339         message = _('An Issue created') + " '" + subject + "' " + _("from Mailgate.")
340         self.log(cr, uid, res, message)
341         self.convert_to_bug(cr, uid, [res], context=context)
342
343         attachents = msg.get('attachments', [])
344         for attactment in attachents or []:
345             data_attach = {
346                 'name': attactment,
347                 'datas': binascii.b2a_base64(str(attachents.get(attactment))),
348                 'datas_fname': attactment,
349                 'description': 'Mail attachment',
350                 'res_model': self._name,
351                 'res_id': res,
352             }
353             self.pool.get('ir.attachment').create(cr, uid, data_attach)
354
355         return res
356
357     def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
358         if context is None:
359             context = {}
360         """
361         @param self: The object pointer
362         @param cr: the current row, from the database cursor,
363         @param uid: the current user’s ID for security checks,
364         @param ids: List of update mail’s IDs
365         """
366
367         if isinstance(ids, (str, int, long)):
368             ids = [ids]
369
370         vals.update({
371             'description': msg['body']
372         })
373         if msg.get('priority', False):
374             vals['priority'] = msg.get('priority')
375
376         maps = {
377             'cost': 'planned_cost',
378             'revenue': 'planned_revenue',
379             'probability': 'probability'
380         }
381         vls = { }
382         for line in msg['body'].split('\n'):
383             line = line.strip()
384             res = tools.misc.command_re.match(line)
385             if res and maps.get(res.group(1).lower(), False):
386                 key = maps.get(res.group(1).lower())
387                 vls[key] = res.group(2).lower()
388
389         vals.update(vls)
390         res = self.write(cr, uid, ids, vals)
391         return res
392
393     def msg_send(self, cr, uid, id, *args, **argv):
394
395         """ Send The Message
396             @param self: The object pointer
397             @param cr: the current row, from the database cursor,
398             @param uid: the current user’s ID for security checks,
399             @param ids: List of email’s IDs
400             @param *args: Return Tuple Value
401             @param **args: Return Dictionary of Keyword Value
402         """
403         return True
404
405 project_issue()
406
407 class project(osv.osv):
408     _inherit = "project.project"
409     _columns = {
410         'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"),
411     }
412 project()
413
414 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: