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']
44 def _compute_day(self, cr, uid, ids, fields, args, context={}):
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 Openday’s IDs
49 @return: difference between current date and log date
50 @param context: A standard dictionary for contextual values
52 cal_obj = self.pool.get('resource.calendar')
53 res_obj = self.pool.get('resource.resource')
56 for lead in self.browse(cr, uid, ids , context):
61 if field == 'day_open':
63 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
64 date_open = datetime.strptime(lead.date_open, "%Y-%m-%d %H:%M:%S")
65 ans = date_open - date_create
66 date_until = lead.date_open
67 elif field == 'day_close':
69 date_create = datetime.strptime(lead.create_date, "%Y-%m-%d %H:%M:%S")
70 date_close = datetime.strptime(lead.date_closed, "%Y-%m-%d %H:%M:%S")
71 date_until = lead.date_closed
72 ans = date_close - date_create
76 resource_ids = res_obj.search(cr, uid, [('user_id','=',lead.user_id.id)])
78 resource_id = resource_ids[0]
80 duration = float(ans.days)
81 if lead.section_id and lead.section_id.resource_calendar_id:
82 duration = float(ans.days) * 24
83 new_dates = cal_obj.interval_get(cr,
85 lead.section_id.resource_calendar_id and lead.section_id.resource_calendar_id.id or False,
86 datetime.strptime(lead.create_date, '%Y-%m-%d %H:%M:%S'),
91 date_until = datetime.strptime(date_until, '%Y-%m-%d %H:%M:%S')
92 for in_time, out_time in new_dates:
93 if in_time.date not in no_days:
94 no_days.append(in_time.date)
95 if out_time > date_until:
97 duration = len(no_days)
98 res[lead.id][field] = abs(int(duration))
102 # Overridden from res.partner.address:
103 'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
104 select=True, help="Optional linked partner, usually after conversion of the lead"),
107 'id': fields.integer('ID'),
108 'name': fields.char('Name', size=64),
109 'active': fields.boolean('Active', required=False),
110 'date_action_last': fields.datetime('Last Action', readonly=1),
111 'date_action_next': fields.datetime('Next Action', readonly=1),
112 'email_from': fields.char('Email', size=128, help="E-mail address of the contact"),
113 'section_id': fields.many2one('crm.case.section', 'Sales Team', \
114 select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
115 'create_date': fields.datetime('Creation Date' , readonly=True),
116 '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"),
117 'description': fields.text('Notes'),
118 'write_date': fields.datetime('Update Date' , readonly=True),
121 'categ_id': fields.many2one('crm.case.categ', 'Category', \
122 domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
123 'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
124 domain="['|',('section_id','=',section_id),('section_id','=',False)]"),
125 'channel_id': fields.many2one('res.partner.canal', 'Channel'),
127 'contact_name': fields.char('Contact Name', size=64),
128 'partner_name': fields.char("Customer Name", size=64),
129 'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
130 'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
131 'type':fields.selection([
133 ('opportunity','Opportunity'),
135 ],'Type', help="Type is used to separate Leads and Opportunities"),
136 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
137 'date_closed': fields.datetime('Closed', readonly=True),
138 'stage_id': fields.many2one('crm.case.stage', 'Stage'),
139 'user_id': fields.many2one('res.users', 'Salesman',help='By Default Salesman is Administrator when create New User'),
140 'referred': fields.char('Referred By', size=64),
141 'date_open': fields.datetime('Opened', readonly=True),
142 'day_open': fields.function(_compute_day, string='Days to Open', \
143 method=True, multi='day_open', type="float", store=True),
144 'day_close': fields.function(_compute_day, string='Days to Close', \
145 method=True, multi='day_close', type="float", store=True),
146 'state': fields.selection(crm.AVAILABLE_STATES, 'State', size=16, readonly=True,
147 help='The state is set to \'Draft\', when a case is created.\
148 \nIf the case is in progress the state is set to \'Open\'.\
149 \nWhen the case is over, the state is set to \'Done\'.\
150 \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
151 'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
155 'active': lambda *a: 1,
156 'user_id': crm_case._get_default_user,
157 'email_from': crm_case._get_default_email,
158 'state': lambda *a: 'draft',
159 'type': lambda *a: 'lead',
160 'section_id': crm_case._get_section,
161 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
162 'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
165 def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
166 """This function returns value of partner email based on Partner Address
167 @param self: The object pointer
168 @param cr: the current row, from the database cursor,
169 @param uid: the current user’s ID for security checks,
170 @param ids: List of case IDs
171 @param add: Id of Partner's address
172 @email: Partner's email ID
175 return {'value': {'email_from': False, 'country_id': False}}
176 address = self.pool.get('res.partner.address').browse(cr, uid, add)
177 return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
179 def case_open(self, cr, uid, ids, *args):
180 """Overrides cancel for crm_case for setting Open Date
181 @param self: The object pointer
182 @param cr: the current row, from the database cursor,
183 @param uid: the current user’s ID for security checks,
184 @param ids: List of case's Ids
185 @param *args: Give Tuple Value
187 old_state = self.read(cr, uid, ids, ['state'])[0]['state']
188 old_stage_id = self.read(cr, uid, ids, ['stage_id'])[0]['stage_id']
189 res = super(crm_lead, self).case_open(cr, uid, ids, *args)
190 if old_state == 'draft':
193 stage_id = super(crm_lead, self).stage_next(cr, uid, ids, *args)
195 value.update({'stage_id': stage_id})
196 value.update(self.onchange_stage_id(cr, uid, ids, stage_id, context={})['value'])
197 value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
198 self.write(cr, uid, ids, value)
200 for case in self.browse(cr, uid, ids):
201 if case.type == 'lead':
202 message = _("The lead '%s' has been opened.") % case.name
203 elif case.type == 'opportunity':
204 message = _("The opportunity '%s' has been opened.") % case.name
206 message = _("The case '%s' has been opened.") % case.name
207 self.log(cr, uid, case.id, message)
210 def case_close(self, cr, uid, ids, *args):
211 """Overrides close for crm_case for setting close date
212 @param self: The object pointer
213 @param cr: the current row, from the database cursor,
214 @param uid: the current user’s ID for security checks,
215 @param ids: List of case Ids
216 @param *args: Tuple Value for additional Params
218 res = super(crm_lead, self).case_close(cr, uid, ids, args)
219 self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
220 for case in self.browse(cr, uid, ids):
221 if case.type == 'lead':
222 message = _("The lead '%s' has been closed.") % case.name
223 elif case.type == 'opportunity':
224 message = _("The opportunity '%s' has been closed.") % case.name
226 message = _("The case '%s' has been closed.") % case.name
227 self.log(cr, uid, case.id, message)
230 def convert_opportunity(self, cr, uid, ids, context=None):
231 """ Precomputation for converting lead to opportunity
232 @param cr: the current row, from the database cursor,
233 @param uid: the current user’s ID for security checks,
234 @param ids: List of closeday’s IDs
235 @param context: A standard dictionary for contextual values
236 @return: Value of action in dict
240 context.update({'active_ids': ids})
242 data_obj = self.pool.get('ir.model.data')
243 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_action')
248 view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
250 for case in self.browse(cr, uid, ids):
251 context.update({'active_id': case.id})
252 if not case.partner_id:
253 data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
256 view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
258 'name': _('Create Partner'),
260 'view_mode': 'form,tree',
261 'res_model': 'crm.lead2opportunity.partner',
264 'views': [(view_id1, 'form')],
265 'type': 'ir.actions.act_window',
272 'name': _('Create Opportunity'),
274 'view_mode': 'form,tree',
275 'res_model': 'crm.lead2opportunity.action',
278 'views': [(view_id, 'form')],
279 'type': 'ir.actions.act_window',
285 def stage_next(self, cr, uid, ids, context=None):
286 stage = super(crm_lead, self).stage_next(cr, uid, ids, context)
288 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
289 self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
290 for case in self.browse(cr, uid, ids, context=context):
291 if case.type == 'lead':
292 message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
293 elif case.type == 'opportunity':
294 message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
295 self.log(cr, uid, case.id, message)
296 if stage_obj.on_change:
297 data = {'probability': stage_obj.probability}
298 self.write(cr, uid, ids, data)
301 def stage_previous(self, cr, uid, ids, context=None):
302 stage = super(crm_lead, self).stage_previous(cr, uid, ids, context)
304 stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
305 self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
306 for case in self.browse(cr, uid, ids, context=context):
307 if case.type == 'lead':
308 message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
309 elif case.type == 'opportunity':
310 message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, case.stage_id.name)
311 self.log(cr, uid, case.id, message)
314 def message_new(self, cr, uid, msg, context):
316 Automatically calls when new email message arrives
318 @param self: The object pointer
319 @param cr: the current row, from the database cursor,
320 @param uid: the current user’s ID for security checks
323 mailgate_pool = self.pool.get('email.server.tools')
325 subject = msg.get('subject')
326 body = msg.get('body')
327 msg_from = msg.get('from')
328 priority = msg.get('priority')
332 'email_from': msg_from,
333 'email_cc': msg.get('cc'),
337 if msg.get('priority', False):
338 vals['priority'] = priority
340 res = mailgate_pool.get_partner(cr, uid, msg.get('from') or msg.get_unixfrom())
344 res = self.create(cr, uid, vals, context)
345 attachents = msg.get('attachments', [])
346 for attactment in attachents or []:
349 'datas':binascii.b2a_base64(str(attachents.get(attactment))),
350 'datas_fname': attactment,
351 'description': 'Mail attachment',
352 'res_model': self._name,
355 self.pool.get('ir.attachment').create(cr, uid, data_attach)
359 def message_update(self, cr, uid, ids, vals={}, msg="", default_act='pending', context={}):
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)):
370 if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
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()):
383 key = maps.get(res.group(1).lower())
384 vls[key] = res.group(2).lower()
387 # Unfortunately the API is based on lists
388 # but we want to update the state based on the
389 # previous state, so we have to loop:
390 for case in self.browse(cr, uid, ids, context=context):
392 if case.state in CRM_LEAD_PENDING_STATES:
393 values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
394 res = self.write(cr, uid, [case.id], values, context=context)
398 def msg_send(self, cr, uid, id, *args, **argv):
401 @param self: The object pointer
402 @param cr: the current row, from the database cursor,
403 @param uid: the current user’s ID for security checks,
404 @param ids: List of email’s IDs
405 @param *args: Return Tuple Value
406 @param **args: Return Dictionary of Keyword Value
411 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: