1 # -*- coding: utf-8 -*-
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 ##############################################################################
22 from osv import fields, osv
23 from datetime import datetime
26 from tools.translate import _
27 from crm import crm_case
32 CRM_LEAD_PENDING_STATES = (
33 crm.AVAILABLE_STATES[2][0], # Cancelled
34 crm.AVAILABLE_STATES[3][0], # Done
35 crm.AVAILABLE_STATES[4][0], # Pending
38 class crm_lead(crm_case, osv.osv):
42 _order = "date_action, priority, id desc"
43 _inherit = ['mailgate.thread','res.partner.address']
45 def fields_view_get(self, cr, uid, view_id=None, view_type='form',
46 context=None, toolbar=False, submenu=False):
48 Changes the view dynamically
49 @param self: The object pointer.
50 @param cr: A database cursor
51 @param uid: ID of the user currently logged in
52 @param context: A standard dictionary
53 @return: New arch of view.
57 res = super(crm_lead, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
58 if view_type == 'form':
59 if res['toolbar']['action'] and res['toolbar']['action'][0]['res_model']=='crm.merge.opportunity':
60 res['toolbar']['action']=[]
63 def _compute_day(self, cr, uid, ids, fields, args, context={}):
65 @param cr: the current row, from the database cursor,
66 @param uid: the current user’s ID for security checks,
67 @param ids: List of Openday’s IDs
68 @return: difference between current date and log date
69 @param context: A standard dictionary for contextual values
71 cal_obj = self.pool.get('resource.calendar')
72 res_obj = self.pool.get('resource.resource')
75 for lead in self.browse(cr, uid, ids , context):
80 if field == 'day_open':
82 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
83 date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
84 ans = date_open - date_create
85 date_until = lead.date_open
86 elif field == 'day_close':
88 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
89 date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
90 date_until = lead.date_closed
91 ans = date_close - date_create
95 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
97 resource_id = resource_ids[0]
99 duration = float(ans.days)
100 if lead.section_id and lead.section_id.resource_calendar_id:
101 duration = float(ans.days) * 24
102 new_dates = cal_obj.interval_get(cr,
104 lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
105 datetime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
110 date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
111 for in_time, out_time in new_dates:
112 if in_time.date not in no_days:
113 no_days.append(in_time.date)
114 if out_time > date_until:
116 duration = len(no_days)
117 res[lead.id][field] = abs(int(duration))
121 # Overridden from res.partner.address:
122 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
123 select=True, help="Optional linked partner, usually after conversion of the lead"),
126 'id': fields.integer('ID'),
127 'name': fields.char('Name', size=64),
128 'active': fields.boolean('Active', required=False),
129 'date_action_last': fields.datetime('Last Action', readonly=1),
130 'date_action_next': fields.datetime('Next Action', readonly=1),
131 'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
132 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
133 select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
134 'create_date': fields.datetime('Creation Date' , readonly=True),
135 'email_cc': fields.text('Global CC', size=252 , 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"),
136 'description': fields.text('Notes'),
137 'write_date': fields.datetime('Update Date' , readonly=True),
140 'categ_id': fields.many2one('crm.case.categ', 'Category', \
141 domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.project.bug')]"),
142 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
143 domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
144 'channel_id': fields.many2one('res.partner.canal', 'Channel'),
146 'contact_name': fields.char('Contact Name', size=64),
147 'partner_name': fields.char("Customer Name", size=64),
148 'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
149 'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
150 'type':fields.selection([
152 ('opportunity','Opportunity'),
154 ],'Type', help="Type is used to separate Leads and Opportunities"),
155 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
156 'date_closed': fields.datetime('Closed', readonly=True),
157 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
158 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
159 'referred': fields.char('Referred By', size=64),
160 'date_open': fields.datetime('Opened', readonly=True),
161 'day_open': fields.function(_compute_day, string='Days to Open', \
162 method=True, multi='day_open', type="float", store=True),
163 'day_close': fields.function(_compute_day, string='Days to Close', \
164 method=True, multi='day_close', type="float", store=True),
165 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
166 help='The state is set to \'Draft\', when a case is created.\
167 \nIf the case is in progress the state is set to \'Open\'.\
168 \nWhen the case is over, the state is set to \'Done\'.\
169 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
170 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
174 'active': lambda *a: 1,
175 'user_id': crm_case._get_default_user,
176 'email_from': crm_case._get_default_email,
177 'state': lambda *a: 'draft',
178 'type': lambda *a: 'lead',
179 'section_id': crm_case._get_section,
180 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
181 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
184 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
185 """This function returns value of partner email based on Partner Address
186 @param self: The object pointer
187 @param cr: the current row, from the database cursor,
188 @param uid: the current user’s ID for security checks,
189 @param ids: List of case IDs
190 @param add: Id of Partner's address
191 @email: Partner's email ID
194 return {'value': {'email_from': False, 'country_id': False}}
195 address = self.pool.get('res.partner.address').browse(cr, uid, add)
196 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
198 def case_open(self, cr, uid, ids, *args):
199 """Overrides cancel for crm_case for setting Open Date
200 @param self: The object pointer
201 @param cr: the current row, from the database cursor,
202 @param uid: the current user’s ID for security checks,
203 @param ids: List of case's Ids
204 @param *args: Give Tuple Value
206 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
207 old_stage_id = self.read(cr, uid, ids, ['stage_id'])[0]['stage_id']
208 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
209 if old_state == 'draft':
212 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
214 value.update({'stage_id': stage_id})
215 value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
216 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
217 self.write(cr, uid, ids, value)
219 for (id, name) in self.name_get(cr, uid, ids):
220 type = self.browse(cr, uid, id).type or 'Lead'
221 message = (_('The ') + type.title()) + " '" + name + "' "+ _("has been Opened.")
222 self.log(cr, uid, id, message)
225 def case_close(self, cr, uid, ids, *args):
226 """Overrides close for crm_case for setting close date
227 @param self: The object pointer
228 @param cr: the current row, from the database cursor,
229 @param uid: the current user’s ID for security checks,
230 @param ids: List of case Ids
231 @param *args: Tuple Value for additional Params
233 res = super(crm_lead, self).case_close(cr, uid, ids, args)
234 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
235 for (id, name) in self.name_get(cr, uid, ids):
236 lead = self.browse(cr, uid, id)
237 if lead.type == 'lead':
238 message = _('The Lead') + " '" + name + "' "+ _("has been Closed.")
239 self.log(cr, uid, id, message)
242 def convert_opportunity(self, cr, uid, ids, context=None):
243 """ Precomputation for converting lead to opportunity
244 @param cr: the current row, from the database cursor,
245 @param uid: the current user’s ID for security checks,
246 @param ids: List of closeday’s IDs
247 @param context: A standard dictionary for contextual values
248 @return: Value of action in dict
252 context.update({'active_ids': ids})
254 data_obj = self.pool.get('ir.model.data')
255 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
260 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
262 for case in self.browse(cr, uid, ids):
263 context.update({'active_id': case.id})
264 if not case.partner_id:
265 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
268 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
270 'name': _('Create Partner'),
272 'view_mode': 'form,tree',
273 'res_model': 'crm.lead2opportunity.partner',
276 'views': [(view_id1, 'form')],
277 'type': 'ir.actions.act_window',
284 'name': _('Create Opportunity'),
286 'view_mode': 'form,tree',
287 'res_model': 'crm.lead2opportunity.action',
290 'views': [(view_id, 'form')],
291 'type': 'ir.actions.act_window',
297 def stage_next(self, cr, uid, ids, context=None):
298 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
300 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
301 if stage_obj.on_change:
302 data = {'probability': stage_obj.probability}
303 self.write(cr, uid, ids, 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') or msg.get_unixfrom())
336 res = self.create(cr, uid, vals, context)
337 message = _('A Lead created') + " '" + subject + "' " + _("from Mailgate.")
338 self.log(cr, uid, res, message)
339 attachents = msg.get('attachments', [])
340 for attactment in attachents or []:
343 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
344 'datas_fname': attactment,
345 'description': 'Mail attachment',
346 'res_model': self._name,
349 self.pool.get('ir.attachment').create(cr, uid, data_attach)
353 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
355 @param self: The object pointer
356 @param cr: the current row, from the database cursor,
357 @param uid: the current user’s ID for security checks,
358 @param ids: List of update mail’s IDs
361 if isinstance(ids, (str, int, long)):
364 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
365 vals['priority'] = msg.get('priority')
368 'cost':'planned_cost',
369 'revenue': 'planned_revenue',
370 'probability':'probability'
373 for line in msg['body'].split('\n'):
375 res = tools.misc.command_re.match(line)
376 if res and maps.get(res.group(1).lower()):
377 key = maps.get(res.group(1).lower())
378 vls[key] = res.group(2).lower()
381 # Unfortunately the API is based on lists
382 # but we want to update the state based on the
383 # previous state, so we have to loop:
384 for case in self.browse(cr, uid, ids, context=context):
386 if case.state in CRM_LEAD_PENDING_STATES:
387 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
388 res = self.write(cr, uid, [case.id], values, context=context)
392 def msg_send(self, cr, uid, id, *args, **argv):
395 @param self: The object pointer
396 @param cr: the current row, from the database cursor,
397 @param uid: the current user’s ID for security checks,
398 @param ids: List of email’s IDs
399 @param *args: Return Tuple Value
400 @param **args: Return Dictionary of Keyword Value
405 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: