2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 from datetime import datetime
24 from osv import fields,osv
25 from tools.translate import _
31 class project_issue(crm.crm_case, osv.osv):
32 _name = "project.issue"
33 _description = "Project Issue"
34 _order = "priority, id desc"
35 _inherit = ['mailgate.thread']
37 def case_open(self, cr, uid, ids, *args):
39 @param self: The object pointer
40 @param cr: the current row, from the database cursor,
41 @param uid: the current user’s ID for security checks,
42 @param ids: List of case's Ids
43 @param *args: Give Tuple Value
46 res = super(project_issue, self).case_open(cr, uid, ids, *args)
47 self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
48 for (id, name) in self.name_get(cr, uid, ids):
49 message = _('Issue ') + " '" + name + "' "+ _("is Open.")
50 self.log(cr, uid, id, message)
53 def case_close(self, cr, uid, ids, *args):
55 @param self: The object pointer
56 @param cr: the current row, from the database cursor,
57 @param uid: the current user’s ID for security checks,
58 @param ids: List of case's Ids
59 @param *args: Give Tuple Value
62 res = super(project_issue, self).case_close(cr, uid, ids, *args)
63 for (id, name) in self.name_get(cr, uid, ids):
64 message = _('Issue ') + " '" + name + "' "+ _("is Closed.")
65 self.log(cr, uid, id, message)
68 def _compute_day(self, cr, uid, ids, fields, args, context=None):
72 @param cr: the current row, from the database cursor,
73 @param uid: the current user’s ID for security checks,
74 @param ids: List of Openday’s IDs
75 @return: difference between current date and log date
76 @param context: A standard dictionary for contextual values
78 cal_obj = self.pool.get('resource.calendar')
79 res_obj = self.pool.get('resource.resource')
82 for issue in self.browse(cr, uid, ids, context=context):
89 if field in ['working_hours_open','day_open']:
91 date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
92 date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
93 ans = date_open - date_create
94 date_until = issue.date_open
95 #Calculating no. of working hours to open the issue
96 hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
97 datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
98 datetime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S'))
99 elif field in ['working_hours_close','day_close']:
100 if issue.date_closed:
101 date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
102 date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
103 date_until = issue.date_closed
104 ans = date_close - date_create
105 #Calculating no. of working hours to close the issue
106 hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
107 datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
108 datetime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S'))
112 resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
113 if resource_ids and len(resource_ids):
114 resource_id = resource_ids[0]
115 duration = float(ans.days)
116 if issue.project_id and issue.project_id.resource_calendar_id:
117 duration = float(ans.days) * 24
118 new_dates = cal_obj.interval_min_get(cr, uid, issue.project_id.resource_calendar_id.id, datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'), duration, resource=resource_id)
120 date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
121 for in_time, out_time in new_dates:
122 if in_time.date not in no_days:
123 no_days.append(in_time.date)
124 if out_time > date_until:
126 duration = len(no_days)
127 if field in ['working_hours_open','working_hours_close']:
128 res[issue.id][field] = hours
130 res[issue.id][field] = abs(float(duration))
134 'id': fields.integer('ID'),
135 'name': fields.char('Name', size=128, required=True),
136 'active': fields.boolean('Active', required=False),
137 'create_date': fields.datetime('Creation Date', readonly=True),
138 'write_date': fields.datetime('Update Date', readonly=True),
139 'date_deadline': fields.date('Deadline'),
140 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
141 select=True, help='Sales team to which Case belongs to.\
142 Define Responsible user and Email account for mail gateway.'),
143 'user_id': fields.many2one('res.users', 'Responsible'),
144 'partner_id': fields.many2one('res.partner', 'Partner'),
145 'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
146 domain="[('partner_id','=',partner_id)]"),
147 'company_id': fields.many2one('res.company', 'Company'),
148 'description': fields.text('Description'),
149 'state': fields.selection([('draft', 'Draft'), ('open', 'To Do'), ('cancel', 'Cancelled'), ('done', 'Closed'),('pending', 'Pending'), ], 'State', size=16, readonly=True,
150 help='The state is set to \'Draft\', when a case is created.\
151 \nIf the case is in progress the state is set to \'Open\'.\
152 \nWhen the case is over, the state is set to \'Done\'.\
153 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
154 'email_from': fields.char('Email', size=128, help="These people will receive email."),
155 '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"),
156 'date_open': fields.datetime('Opened', readonly=True),
157 # Project Issue fields
158 'date_closed': fields.datetime('Closed', readonly=True),
159 'date': fields.datetime('Date'),
160 'canal_id': fields.many2one('res.partner.canal', 'Channel', help="The channels represent the different communication modes available with the customer." \
161 " With each commercial opportunity, you can indicate the canall which is this opportunity source."),
162 'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('object_id.model', '=', 'crm.project.bug')]"),
163 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Severity'),
164 'type_id': fields.many2one('crm.case.resource.type', 'Version', domain="[('object_id.model', '=', 'project.issue')]"),
165 'partner_name': fields.char("Employee's Name", size=64),
166 'partner_mobile': fields.char('Mobile', size=32),
167 'partner_phone': fields.char('Phone', size=32),
168 'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('object_id.model', '=', 'project.issue')]"),
169 'project_id':fields.many2one('project.project', 'Project'),
170 'duration': fields.float('Duration'),
171 'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
172 'day_open': fields.function(_compute_day, string='Days to Open', \
173 method=True, multi='day_open', type="float", store=True),
174 'day_close': fields.function(_compute_day, string='Days to Close', \
175 method=True, multi='day_close', type="float", store=True),
176 'assigned_to': fields.many2one('res.users', 'Assigned to', help='This is the current user to whom the related task have been assigned'),
177 'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
178 method=True, multi='working_days_open', type="float", store=True),
179 'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
180 method=True, multi='working_days_close', type="float", store=True),
181 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
182 'date_action_last': fields.datetime('Last Action', readonly=1),
183 'date_action_next': fields.datetime('Next Action', readonly=1),
186 def _get_project(self, cr, uid, context):
187 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
188 if user.context_project_id:
189 return user.context_project_id.id
194 'user_id': crm.crm_case._get_default_user,
195 'partner_id': crm.crm_case._get_default_partner,
196 'partner_address_id': crm.crm_case._get_default_partner_address,
197 'email_from': crm.crm_case. _get_default_email,
199 'section_id': crm.crm_case. _get_section,
200 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
201 'priority': crm.AVAILABLE_PRIORITIES[2][0],
202 'project_id':_get_project,
205 def convert_issue_task(self, cr, uid, ids, context=None):
206 case_obj = self.pool.get('project.issue')
207 data_obj = self.pool.get('ir.model.data')
208 task_obj = self.pool.get('project.task')
213 result = data_obj._get_id(cr, uid, 'project', 'view_task_search_form')
214 res = data_obj.read(cr, uid, result, ['res_id'])
215 id2 = data_obj._get_id(cr, uid, 'project', 'view_task_form2')
216 id3 = data_obj._get_id(cr, uid, 'project', 'view_task_tree2')
218 id2 = data_obj.browse(cr, uid, id2, context=context).res_id
220 id3 = data_obj.browse(cr, uid, id3, context=context).res_id
222 for bug in case_obj.browse(cr, uid, ids, context=context):
223 new_task_id = task_obj.create(cr, uid, {
225 'partner_id': bug.partner_id.id,
226 'description':bug.description,
228 'project_id': bug.project_id.id,
229 'priority': bug.priority,
230 'user_id': bug.assigned_to.id,
231 'planned_hours': 0.0,
235 'task_id': new_task_id,
237 case_obj.write(cr, uid, [bug.id], vals)
242 'view_mode': 'form,tree',
243 'res_model': 'project.task',
244 'res_id': int(new_task_id),
246 'views': [(id2,'form'),(id3,'tree'),(False,'calendar'),(False,'graph')],
247 'type': 'ir.actions.act_window',
248 'search_view_id': res['res_id'],
252 def _convert(self, cr, uid, ids, xml_id, context=None):
253 data_obj = self.pool.get('ir.model.data')
254 id2 = data_obj._get_id(cr, uid, 'project_issue', xml_id)
257 categ_id = data_obj.browse(cr, uid, id2, context=context).res_id
259 self.write(cr, uid, ids, {'categ_id': categ_id})
262 def convert_to_feature(self, cr, uid, ids, context=None):
263 return self._convert(cr, uid, ids, 'feature_request_categ', context=context)
265 def convert_to_bug(self, cr, uid, ids, context=None):
266 return self._convert(cr, uid, ids, 'bug_categ', context=context)
268 def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
273 stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
274 if not stage.on_change:
278 def case_escalate(self, cr, uid, ids, *args):
279 """Escalates case to top level
280 @param self: The object pointer
281 @param cr: the current row, from the database cursor,
282 @param uid: the current user’s ID for security checks,
283 @param ids: List of case Ids
284 @param *args: Tuple Value for additional Params
286 cases = self.browse(cr, uid, ids)
289 if case.project_id.project_escalation_id:
290 data['project_id'] = case.project_id.project_escalation_id.id
291 if case.project_id.project_escalation_id.user_id:
292 data['user_id'] = case.project_id.project_escalation_id.user_id.id
294 self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False})
296 raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
297 self.write(cr, uid, [case.id], data)
300 def message_new(self, cr, uid, msg, context):
302 Automatically calls when new email message arrives
304 @param self: The object pointer
305 @param cr: the current row, from the database cursor,
306 @param uid: the current user’s ID for security checks
309 mailgate_pool = self.pool.get('email.server.tools')
311 subject = msg.get('subject') or _('No Title')
312 body = msg.get('body')
313 msg_from = msg.get('from')
314 priority = msg.get('priority')
318 'email_from': msg_from,
319 'email_cc': msg.get('cc'),
323 if msg.get('priority', False):
324 vals['priority'] = priority
326 res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
329 context.update({'state_to' : 'draft'})
330 res = self.create(cr, uid, vals, context)
331 message = _('An Issue created') + " '" + subject + "' " + _("from Mailgate.")
332 self.log(cr, uid, res, message)
333 self.convert_to_bug(cr, uid, [res], context=context)
335 attachents = msg.get('attachments', [])
336 for attactment in attachents or []:
339 'datas': binascii.b2a_base64(str(attachents.get(attactment))),
340 'datas_fname': attactment,
341 'description': 'Mail attachment',
342 'res_model': self._name,
345 self.pool.get('ir.attachment').create(cr, uid, data_attach)
349 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
353 @param self: The object pointer
354 @param cr: the current row, from the database cursor,
355 @param uid: the current user’s ID for security checks,
356 @param ids: List of update mail’s IDs
359 if isinstance(ids, (str, int, long)):
363 'description': msg['body']
365 if msg.get('priority', False):
366 vals['priority'] = msg.get('priority')
369 'cost': 'planned_cost',
370 'revenue': 'planned_revenue',
371 'probability': 'probability'
374 for line in msg['body'].split('\n'):
376 res = tools.misc.command_re.match(line)
377 if res and maps.get(res.group(1).lower(), False):
378 key = maps.get(res.group(1).lower())
379 vls[key] = res.group(2).lower()
382 res = self.write(cr, uid, ids, vals)
385 def msg_send(self, cr, uid, id, *args, **argv):
388 @param self: The object pointer
389 @param cr: the current row, from the database cursor,
390 @param uid: the current user’s ID for security checks,
391 @param ids: List of email’s IDs
392 @param *args: Return Tuple Value
393 @param **args: Return Dictionary of Keyword Value
399 class project(osv.osv):
400 _inherit = "project.project"
402 'resource_calendar_id' : fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
403 '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)]}),
404 'reply_to' : fields.char('Reply-To Email Address', size=256)
407 def _check_escalation(self, cr, uid, ids):
408 project_obj = self.browse(cr, uid, ids[0])
409 if project_obj.project_escalation_id:
410 if project_obj.project_escalation_id.id == project_obj.id:
415 (_check_escalation, 'Error! You cannot assign escalation to the same project!', ['project_escalation_id'])
419 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: