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 ##############################################################################
27 from datetime import datetime, timedelta
33 from osv import fields,osv,orm
34 from osv.orm import except_orm
35 from tools.translate import _
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']
44 def case_open(self, cr, uid, ids, *args):
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
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)
60 def case_close(self, cr, uid, ids, *args):
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
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)
75 def _compute_day(self, cr, uid, ids, fields, args, context=None):
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
85 cal_obj = self.pool.get('resource.calendar')
86 res_obj = self.pool.get('resource.resource')
89 for issue in self.browse(cr, uid, ids, context=context):
96 if field in ['working_hours_open','day_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'))
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)
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:
133 duration = len(no_days)
134 if field in ['working_hours_open','working_hours_close']:
135 res[issue.id][field] = hours
137 res[issue.id][field] = abs(float(duration))
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),
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
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,
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,
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')
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')
229 id2 = data_obj.browse(cr, uid, id2, context=context).res_id
231 id3 = data_obj.browse(cr, uid, id3, context=context).res_id
233 for bug in case_obj.browse(cr, uid, ids, context=context):
234 new_task_id = task_obj.create(cr, uid, {
236 'partner_id': bug.partner_id.id,
237 'description':bug.description,
239 'project_id': bug.project_id.id,
240 'priority': bug.priority,
241 'user_id': bug.assigned_to.id,
242 'planned_hours': 0.0,
246 'task_id': new_task_id,
248 case_obj.write(cr, uid, [bug.id], vals)
253 'view_mode': 'form,tree',
254 'res_model': 'project.task',
255 'res_id': int(new_task_id),
257 'views': [(id2,'form'),(id3,'tree'),(False,'calendar'),(False,'graph')],
258 'type': 'ir.actions.act_window',
259 'search_view_id': res['res_id'],
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)
268 categ_id = data_obj.browse(cr, uid, id2, context=context).res_id
270 self.write(cr, uid, ids, {'categ_id': categ_id})
273 def convert_to_feature(self, cr, uid, ids, context=None):
274 return self._convert(cr, uid, ids, 'feature_request_categ', context=context)
276 def convert_to_bug(self, cr, uid, ids, context=None):
277 return self._convert(cr, uid, ids, 'bug_categ', context=context)
279 def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
284 stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
285 if not stage.on_change:
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
297 cases = self.browse(cr, uid, ids)
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
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)
309 def message_new(self, cr, uid, msg, context):
311 Automatically calls when new email message arrives
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
318 mailgate_pool = self.pool.get('email.server.tools')
320 subject = msg.get('subject')
321 body = msg.get('body')
322 msg_from = msg.get('from')
323 priority = msg.get('priority')
327 'email_from': msg_from,
328 'email_cc': msg.get('cc'),
332 if msg.get('priority', False):
333 vals['priority'] = priority
335 res = mailgate_pool.get_partner(cr, uid, msg.get('from'))
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)
343 attachents = msg.get('attachments', [])
344 for attactment in attachents or []:
347 'datas': binascii.b2a_base64(str(attachents.get(attactment))),
348 'datas_fname': attactment,
349 'description': 'Mail attachment',
350 'res_model': self._name,
353 self.pool.get('ir.attachment').create(cr, uid, data_attach)
357 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context=None):
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
367 if isinstance(ids, (str, int, long)):
371 'description': msg['body']
373 if msg.get('priority', False):
374 vals['priority'] = msg.get('priority')
377 'cost': 'planned_cost',
378 'revenue': 'planned_revenue',
379 'probability': 'probability'
382 for line in msg['body'].split('\n'):
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()
390 res = self.write(cr, uid, ids, vals)
393 def msg_send(self, cr, uid, id, *args, **argv):
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
407 class project(osv.osv):
408 _inherit = "project.project"
410 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report"),
414 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: