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 ##############################################################################
26 from datetime import datetime, timedelta
32 from osv import fields,osv,orm
33 from osv.orm import except_orm
34 from tools.translate import _
37 class project_issue(osv.osv, crm.crm_case):
38 _name = "project.issue"
39 _description = "Project Issue"
40 _order = "priority, id desc"
41 _inherit = ['mailgate.thread']
43 def case_open(self, cr, uid, ids, *args):
45 @param self: The object pointer
46 @param cr: the current row, from the database cursor,
47 @param uid: the current user’s ID for security checks,
48 @param ids: List of case's Ids
49 @param *args: Give Tuple Value
52 res = super(project_issue, self).case_open(cr, uid, ids, *args)
53 self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
54 for (id, name) in self.name_get(cr, uid, ids):
55 message = _('Issue ') + " '" + name + "' "+ _("is Open.")
56 self.log(cr, uid, id, message)
59 def case_close(self, cr, uid, ids, *args):
61 @param self: The object pointer
62 @param cr: the current row, from the database cursor,
63 @param uid: the current user’s ID for security checks,
64 @param ids: List of case's Ids
65 @param *args: Give Tuple Value
68 res = super(project_issue, self).case_close(cr, uid, ids, *args)
69 for (id, name) in self.name_get(cr, uid, ids):
70 message = _('Issue ') + " '" + name + "' "+ _("is Closed.")
71 self.log(cr, uid, id, message)
74 def _compute_day(self, cr, uid, ids, fields, args, context=None):
78 @param cr: the current row, from the database cursor,
79 @param uid: the current user’s ID for security checks,
80 @param ids: List of Openday’s IDs
81 @return: difference between current date and log date
82 @param context: A standard dictionary for contextual values
84 cal_obj = self.pool.get('resource.calendar')
85 res_obj = self.pool.get('resource.resource')
88 for issue in self.browse(cr, uid, ids, context=context):
95 if field in ['working_hours_open','day_open']:
97 date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
98 date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
99 ans = date_open - date_create
100 date_until = issue.date_open
101 #Calculating no. of working hours to open the issue
102 hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
103 datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
104 datetime.strptime(issue.date_open, '%Y-%m-%d %H:%M:%S'))
105 elif field in ['working_hours_close','day_close']:
106 if issue.date_closed:
107 date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
108 date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
109 date_until = issue.date_closed
110 ans = date_close - date_create
111 #Calculating no. of working hours to close the issue
112 hours = cal_obj.interval_hours_get(cr, uid, issue.project_id.resource_calendar_id.id,
113 datetime.strptime(issue.create_date, '%Y-%m-%d %H:%M:%S'),
114 datetime.strptime(issue.date_closed, '%Y-%m-%d %H:%M:%S'))
118 resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
119 if resource_ids and len(resource_ids):
120 resource_id = resource_ids[0]
121 duration = float(ans.days)
122 if issue.project_id and issue.project_id.resource_calendar_id:
123 duration = float(ans.days) * 24
124 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)
126 date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
127 for in_time, out_time in new_dates:
128 if in_time.date not in no_days:
129 no_days.append(in_time.date)
130 if out_time > date_until:
132 duration = len(no_days)
133 if field in ['working_hours_open','working_hours_close']:
134 res[issue.id][field] = hours
136 res[issue.id][field] = abs(float(duration))
140 'id': fields.integer('ID'),
141 'name': fields.char('Name', size=128, required=True),
142 'active': fields.boolean('Active', required=False),
143 'create_date': fields.datetime('Creation Date', readonly=True),
144 'write_date': fields.datetime('Update Date', readonly=True),
145 'date_deadline': fields.date('Deadline'),
146 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
147 select=True, help='Sales team to which Case belongs to.\
148 Define Responsible user and Email account for mail gateway.'),
149 'user_id': fields.many2one('res.users', 'Responsible'),
150 'partner_id': fields.many2one('res.partner', 'Partner'),
151 'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
152 domain="[('partner_id','=',partner_id)]"),
153 'company_id': fields.many2one('res.company', 'Company'),
154 'description': fields.text('Description'),
155 'state': fields.selection([('draft', 'Draft'), ('open', 'Todo'), ('cancel', 'Cancelled'), ('done', 'Closed'),('pending', 'Pending'), ], 'State', size=16, readonly=True,
156 help='The state is set to \'Draft\', when a case is created.\
157 \nIf the case is in progress the state is set to \'Open\'.\
158 \nWhen the case is over, the state is set to \'Done\'.\
159 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
160 'email_from': fields.char('Email', size=128, help="These people will receive email."),
161 '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"),
162 'date_open': fields.datetime('Opened', readonly=True),
163 # Project Issue fields
164 'date_closed': fields.datetime('Closed', readonly=True),
165 'date': fields.datetime('Date'),
166 'canal_id': fields.many2one('res.partner.canal', 'Channel', help="The channels represent the different communication modes available with the customer." \
167 " With each commercial opportunity, you can indicate the canall which is this opportunity source."),
168 'categ_id': fields.many2one('crm.case.categ', 'Category', domain="[('object_id.model', '=', 'crm.project.bug')]"),
169 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Severity'),
170 'type_id': fields.many2one('crm.case.resource.type', 'Version', domain="[('object_id.model', '=', 'project.issue')]"),
171 'partner_name': fields.char("Employee's Name", size=64),
172 'partner_mobile': fields.char('Mobile', size=32),
173 'partner_phone': fields.char('Phone', size=32),
174 'stage_id': fields.many2one ('crm.case.stage', 'Stage', domain="[('object_id.model', '=', 'project.issue')]"),
175 'project_id':fields.many2one('project.project', 'Project'),
176 'duration': fields.float('Duration'),
177 'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
178 'day_open': fields.function(_compute_day, string='Days to Open', \
179 method=True, multi='day_open', type="float", store=True),
180 'day_close': fields.function(_compute_day, string='Days to Close', \
181 method=True, multi='day_close', type="float", store=True),
182 'assigned_to': fields.many2one('res.users', 'Assigned to', help='This is the current user to whom the related task have been assigned'),
183 'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
184 method=True, multi='working_days_open', type="float", store=True),
185 'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
186 method=True, multi='working_days_close', type="float", store=True),
187 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
188 'date_action_last': fields.datetime('Last Action', readonly=1),
189 'date_action_next': fields.datetime('Next Action', readonly=1),
192 def _get_project(self, cr, uid, context):
193 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
194 if user.context_project_id:
195 return user.context_project_id.id
200 'user_id': crm.crm_case._get_default_user,
201 'partner_id': crm.crm_case._get_default_partner,
202 'partner_address_id': crm.crm_case._get_default_partner_address,
203 'email_from': crm.crm_case. _get_default_email,
205 'section_id': crm.crm_case. _get_section,
206 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
207 'priority': crm.AVAILABLE_PRIORITIES[2][0],
208 'project_id':_get_project,
211 def convert_issue_task(self, cr, uid, ids, context=None):
212 case_obj = self.pool.get('project.issue')
213 data_obj = self.pool.get('ir.model.data')
214 task_obj = self.pool.get('project.task')
219 result = data_obj._get_id(cr, uid, 'project', 'view_task_search_form')
220 res = data_obj.read(cr, uid, result, ['res_id'])
221 id2 = data_obj._get_id(cr, uid, 'project', 'view_task_form2')
222 id3 = data_obj._get_id(cr, uid, 'project', 'view_task_tree2')
224 id2 = data_obj.browse(cr, uid, id2, context=context).res_id
226 id3 = data_obj.browse(cr, uid, id3, context=context).res_id
228 for bug in case_obj.browse(cr, uid, ids, context=context):
229 new_task_id = task_obj.create(cr, uid, {
231 'partner_id': bug.partner_id.id,
232 'description':bug.description,
234 'project_id': bug.project_id.id,
235 'priority': bug.priority,
236 'user_id': bug.assigned_to.id,
237 'planned_hours': 0.0,
241 'task_id': new_task_id,
243 case_obj.write(cr, uid, [bug.id], vals)
248 'view_mode': 'form,tree',
249 'res_model': 'project.task',
250 'res_id': int(new_task_id),
252 'views': [(id2,'form'),(id3,'tree'),(False,'calendar'),(False,'graph')],
253 'type': 'ir.actions.act_window',
254 'search_view_id': res['res_id'],
258 def _convert(self, cr, uid, ids, xml_id, context=None):
259 data_obj = self.pool.get('ir.model.data')
260 id2 = data_obj._get_id(cr, uid, 'project_issue', xml_id)
263 categ_id = data_obj.browse(cr, uid, id2, context=context).res_id
265 self.write(cr, uid, ids, {'categ_id': categ_id})
268 def convert_to_feature(self, cr, uid, ids, context=None):
269 return self._convert(cr, uid, ids, 'feature_request_categ', context=context)
271 def convert_to_bug(self, cr, uid, ids, context=None):
272 return self._convert(cr, uid, ids, 'bug_categ', context=context)
274 def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
279 stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
280 if not stage.on_change:
284 def case_escalate(self, cr, uid, ids, *args):
285 """Escalates case to top level
286 @param self: The object pointer
287 @param cr: the current row, from the database cursor,
288 @param uid: the current user’s ID for security checks,
289 @param ids: List of case Ids
290 @param *args: Tuple Value for additional Params
292 cases = self.browse(cr, uid, ids)
295 if case.project_id.project_escalation_id:
296 data['project_id'] = case.project_id.project_escalation_id.id
297 if case.project_id.project_escalation_id.user_id:
298 data['user_id'] = case.project_id.project_escalation_id.user_id.id
300 self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False})
302 raise osv.except_osv(_('Warning !'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!'))
303 self.write(cr, uid, [case.id], data)
306 def message_new(self, cr, uid, msg, context):
308 Automatically calls when new email message arrives
310 @param self: The object pointer
311 @param cr: the current row, from the database cursor,
312 @param uid: the current user’s ID for security checks
315 mailgate_pool = self.pool.get('email.server.tools')
317 subject = msg.get('subject')
318 body = msg.get('body')
319 msg_from = msg.get('from')
320 priority = msg.get('priority')
324 'email_from': msg_from,
325 'email_cc': msg.get('cc'),
329 if msg.get('priority', False):
330 vals['priority'] = priority
332 res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
335 res = self.create(cr, uid, vals, context)
336 message = _('An Issue created') + " '" + subject + "' " + _("from Mailgate.")
337 self.log(cr, uid, res, message)
338 self.convert_to_bug(cr, uid, [res], context=context)
340 attachents = msg.get('attachments', [])
341 for attactment in attachents or []:
344 'datas': binascii.b2a_base64(str(attachents.get(attactment))),
345 'datas_fname': attactment,
346 'description': 'Mail attachment',
347 'res_model': self._name,
350 self.pool.get('ir.attachment').create(cr, uid, data_attach)
354 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
358 @param self: The object pointer
359 @param cr: the current row, from the database cursor,
360 @param uid: the current user’s ID for security checks,
361 @param ids: List of update mail’s IDs
364 if isinstance(ids, (str, int, long)):
368 'description': msg['body']
370 if msg.get('priority', False):
371 vals['priority'] = msg.get('priority')
374 'cost': 'planned_cost',
375 'revenue': 'planned_revenue',
376 'probability': 'probability'
379 for line in msg['body'].split('\n'):
381 res = tools.misc.command_re.match(line)
382 if res and maps.get(res.group(1).lower(), False):
383 key = maps.get(res.group(1).lower())
384 vls[key] = res.group(2).lower()
387 res = self.write(cr, uid, ids, vals)
390 def msg_send(self, cr, uid, id, *args, **argv):
393 @param self: The object pointer
394 @param cr: the current row, from the database cursor,
395 @param uid: the current user’s ID for security checks,
396 @param ids: List of email’s IDs
397 @param *args: Return Tuple Value
398 @param **args: Return Dictionary of Keyword Value
404 class project(osv.osv):
405 _inherit = "project.project"
407 '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)]}),
408 '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)]}),
409 'reply_to' : fields.char('Reply-To Email Address', size=256)
412 def _check_escalation(self, cr, uid, ids):
413 project_obj = self.browse(cr, uid, ids[0])
414 if project_obj.project_escalation_id:
415 if project_obj.project_escalation_id.id == project_obj.id:
420 (_check_escalation, 'Error! You cannot assign escalation to the same project!', ['project_escalation_id'])
424 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: