from crm import crm_case
import binascii
import tools
-
+from mail.mail_message import to_email
CRM_LEAD_PENDING_STATES = (
crm.AVAILABLE_STATES[2][0], # Cancelled
""" CRM Lead Case """
_name = "crm.lead"
_description = "Lead/Opportunity"
- _order = "date_action, priority, id desc"
+ _order = "priority,date_action,id desc"
_inherit = ['mail.thread','res.partner.address']
+
+ def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
+ access_rights_uid = access_rights_uid or uid
+ stage_obj = self.pool.get('crm.case.stage')
+ order = stage_obj._order
+ if read_group_order == 'stage_id desc':
+ # lame hack to allow reverting search, should just work in the trivial case
+ order = "%s desc" % order
+ stage_ids = stage_obj._search(cr, uid, ['|', ('id','in',ids),('case_default','=',1)], order=order,
+ access_rights_uid=access_rights_uid, context=context)
+ result = stage_obj.name_get(cr, access_rights_uid, stage_ids, context=context)
+ # restore order of the search
+ result.sort(lambda x,y: cmp(stage_ids.index(x[0]), stage_ids.index(y[0])))
+ return result
+
+ _group_by_full = {
+ 'stage_id': _read_group_stage_ids
+ }
+
+ # overridden because res.partner.address has an inconvenient name_get,
+ # especially if base_contact is installed.
+ def name_get(self, cr, user, ids, context=None):
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ return [(r['id'], tools.ustr(r[self._rec_name]))
+ for r in self.read(cr, user, ids, [self._rec_name], context)]
+
def _compute_day(self, cr, uid, ids, fields, args, context=None):
"""
@param cr: the current row, from the database cursor,
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
select=True, help="Optional linked partner, usually after conversion of the lead"),
- # From crm.case
- 'id': fields.integer('ID'),
+ 'id': fields.integer('ID', readonly=True),
'name': fields.char('Name', size=64, select=1),
'active': fields.boolean('Active', required=False),
'date_action_last': fields.datetime('Last Action', readonly=1),
'date_action_next': fields.datetime('Next Action', readonly=1),
'email_from': fields.char('Email', size=128, help="E-mail address of the contact", select=1),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
- select=True, help='Sales team to which this case belongs to. Defines responsible user and e-mail address for the mail gateway.'),
+ select=True, help='When sending mails, the default email address is taken from the sales team.'),
'create_date': fields.datetime('Creation Date' , readonly=True),
'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"),
'description': fields.text('Notes'),
'write_date': fields.datetime('Update Date' , readonly=True),
- # Lead fields
'categ_id': fields.many2one('crm.case.categ', 'Category', \
domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]"),
'type_id': fields.many2one('crm.case.resource.type', 'Campaign', \
domain="['|',('section_id','=',section_id),('section_id','=',False)]", help="From which campaign (seminar, marketing campaign, mass mailing, ...) did this contact come from?"),
- 'channel_id': fields.many2one('res.partner.canal', 'Channel', help="From which channel (mail, direct, phone, ...) did this contact reach you?"),
+ 'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"),
'contact_name': fields.char('Contact Name', size=64),
- 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner that will be created while converting the into opportunity', select=1),
+ 'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner that will be created while converting the lead into opportunity', select=1),
'optin': fields.boolean('Opt-In', help="If opt-in is checked, this contact has accepted to receive emails."),
'optout': fields.boolean('Opt-Out', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
- 'type':fields.selection([
- ('lead','Lead'),
- ('opportunity','Opportunity'),
-
- ],'Type', help="Type is used to separate Leads and Opportunities"),
- 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority'),
+ 'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"),
+ 'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
'date_closed': fields.datetime('Closed', readonly=True),
- 'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('type','=','lead')]"),
- 'user_id': fields.many2one('res.users', 'Salesman', select=1),
+ 'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('section_ids', '=', section_id)]"),
+ 'user_id': fields.many2one('res.users', 'Salesman'),
'referred': fields.char('Referred By', size=64),
'date_open': fields.datetime('Opened', readonly=True),
'day_open': fields.function(_compute_day, string='Days to Open', \
\nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
'subjects': fields.function(_get_email_subject, fnct_search=_history_search, string='Subject of Email', type='char', size=64),
- }
+ # Only used for type opportunity
+ 'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', domain="[('partner_id','=',partner_id)]"),
+ 'probability': fields.float('Probability (%)',group_operator="avg"),
+ 'planned_revenue': fields.float('Expected Revenue'),
+ 'ref': fields.reference('Reference', selection=crm._links_get, size=128),
+ 'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
+ 'phone': fields.char("Phone", size=64),
+ 'date_deadline': fields.date('Expected Closing'),
+ 'date_action': fields.date('Next Action Date', select=True),
+ 'title_action': fields.char('Next Action', size=64),
+ 'stage_id': fields.many2one('crm.case.stage', 'Stage', domain="[('section_ids', '=', section_id)]"),
+ 'color': fields.integer('Color Index'),
+ 'partner_address_name': fields.related('partner_address_id', 'name', type='char', string='Partner Contact Name', readonly=True),
+ 'partner_address_email': fields.related('partner_address_id', 'email', type='char', string='Partner Contact Email', readonly=True),
+ 'company_currency': fields.related('company_id', 'currency_id', 'symbol', type='char', string='Company Currency', readonly=True),
+ 'user_email': fields.related('user_id', 'user_email', type='char', string='User Email', readonly=True),
+ 'user_login': fields.related('user_id', 'login', type='char', string='User Login', readonly=True),
+
+ }
_defaults = {
'active': lambda *a: 1,
'section_id': crm_case._get_section,
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
- #'stage_id': _get_stage_id,
+ 'color': 0,
}
+ def create(self, cr, uid, vals, context=None):
+ obj_id = super(crm_lead, self).create(cr, uid, vals, context)
+ self._case_create_notification(cr, uid, [obj_id], context=context)
+ return obj_id
def onchange_partner_address_id(self, cr, uid, ids, add, email=False):
"""This function returns value of partner email based on Partner Address
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of case IDs
- @param add: Id of Partner's address
- @email: Partner's email ID
"""
if not add:
return {'value': {'email_from': False, 'country_id': False}}
address = self.pool.get('res.partner.address').browse(cr, uid, add)
return {'value': {'email_from': address.email, 'phone': address.phone, 'country_id': address.country_id.id}}
- def case_open(self, cr, uid, ids, *args):
- """Overrides cancel for crm_case for setting Open Date
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of case's Ids
- @param *args: Give Tuple Value
+ def on_change_optin(self, cr, uid, ids, optin):
+ return {'value':{'optin':optin,'optout':False}}
+
+ def on_change_optout(self, cr, uid, ids, optout):
+ return {'value':{'optout':optout,'optin':False}}
+
+ def onchange_stage_id(self, cr, uid, ids, stage_id, context={}):
+ if not stage_id:
+ return {'value':{}}
+ stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
+ if not stage.on_change:
+ return {'value':{}}
+ return {'value':{'probability': stage.probability}}
+
+ def stage_find_percent(self, cr, uid, percent, section_id):
+ """ Return the first stage with a probability == percent
"""
- leads = self.browse(cr, uid, ids)
+ stage_pool = self.pool.get('crm.case.stage')
+ if section_id :
+ ids = stage_pool.search(cr, uid, [("probability", '=', percent), ("section_ids", 'in', [section_id])])
+ else :
+ ids = stage_pool.search(cr, uid, [("probability", '=', percent)])
+ if ids:
+ return ids[0]
+ return False
+ def stage_find_lost(self, cr, uid, section_id):
+ return self.stage_find_percent(cr, uid, 0.0, section_id)
- for i in xrange(0, len(ids)):
- if leads[i].state == 'draft':
- value = {}
- if not leads[i].stage_id :
- stage_id = self._find_first_stage(cr, uid, leads[i].type, leads[i].section_id.id or False)
- value.update({'stage_id' : stage_id})
- value.update({'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
- self.write(cr, uid, [ids[i]], value)
- self.log_open( cr, uid, leads[i])
- res = super(crm_lead, self).case_open(cr, uid, ids, *args)
- return res
+ def stage_find_won(self, cr, uid, section_id):
+ return self.stage_find_percent(cr, uid, 100.0, section_id)
- def log_open(self, cr, uid, case):
- if case.type == 'lead':
- message = _("The lead '%s' has been opened.") % case.name
- elif case.type == 'opportunity':
- message = _("The opportunity '%s' has been opened.") % case.name
+ def _case_create_notification(self, cr, uid, ids, context=None):
+ for obj in self.browse(cr, uid, ids, context=context):
+ self.message_subscribe(cr, uid, ids, [obj.user_id.id], context=context)
+ if obj.type=="opportunity" and obj.state=="draft":
+ message = _("Opportunity is <b>created</b>.")
+ elif obj.type=="lead" :
+ message = _("Lead is <b>created</b>.")
+ else:
+ message = _("The case has been <b>created</b>.")
+ self.message_append_note(cr, uid, ids, _('System notification'),
+ message, type='notification', need_action_user_id=obj.user_id.id, context=context)
+ return True
+
+ def _case_open_notification(self, lead, context=None):
+ if lead.state != 'draft' and lead.state != 'pending':
+ return False
+ if lead.type == 'lead':
+ message = _("The lead has been <b>opened</b>.")
+ elif lead.type == 'opportunity':
+ message = _("The opportunity has been <b>opened</b>.")
else:
- message = _("The case '%s' has been opened.") % case.name
- self.log(cr, uid, case.id, message)
+ message = _("The case has been <b>opened</b>.")
+ lead.message_append_note('' ,message, need_action_user_id=lead.user_id.id)
+
+ def _case_close_notification(self, lead, context=None):
+ lead[0].message_mark_done(context)
+ if lead[0].type == 'lead':
+ message = _("The lead has been <b>closed</b>.")
+ elif lead[0].type == 'opportunity':
+ message = _("The opportunity has been <b>closed</b>.")
+ else:
+ message = _("The case has been <b>closed</b>.")
+ lead[0].message_append_note('' ,message)
+
+ def _case_mark_lost_notification(self, lead, context=None):
+ lead.message_mark_done(context)
+ message = _("The opportunity has been <b>marked as lost</b>.")
+ lead.message_append_note('' ,message)
+
+ def _case_mark_won_notification(self, lead, context=None):
+ lead.message_mark_done(context)
+ message = _("The opportunity has been <b>won</b>.")
+ lead.message_append_note('' ,message)
+
+ def _case_cancel_notification(self, lead, context=None):
+ lead[0].message_mark_done(context)
+ if lead[0].type == 'lead':
+ message = _("The lead has been <b>cancelled</b>.")
+ elif lead[0].type == 'opportunity':
+ message = _("The opportunity has been <b>cancelled</b>.")
+ lead[0].message_append_note('' ,message)
+
+ def _case_pending_notification(self, case, context=None):
+ if case[0].type == 'lead':
+ message = _("The lead is <b>pending</b>.")
+ elif case[0].type == 'opportunity':
+ message = _("The opportunity is <b>pending</b>.")
+ case[0].message_append_note('' ,message)
+
+ def _case_escalate_notification(self, case, context=None):
+ message = _("The lead is <b>escalated</b>.")
+ case.message_append_note('' ,message)
+
+ def _case_phonecall_notification(self, cr, uid, ids, case, phonecall, action, context=None):
+ for obj in phonecall.browse(cr, uid, ids, context=context):
+ if action == "schedule" :
+ message = _("<b>%s a call</b> for the %s.") % (action, obj.date)
+ else :
+ message = _("<b>%s a call</b>.") % (action)
+ case.message_append_note('', message)
+ if action == "schedule" :
+ phonecall.message_append_note(cr, uid, ids, '', message, need_action_user_id=obj.user_id.id)
+
+ def case_open(self, cr, uid, ids, context=None):
+ res = super(crm_lead, self).case_open(cr, uid, ids, context)
+ for lead in self.browse(cr, uid, ids, context=context):
+ value = {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')}
+ self.write(cr, uid, [lead.id], value)
+ if lead.type == 'opportunity' and not lead.stage_id:
+ stage_id = self.stage_find(cr, uid, lead.section_id.id or False, [('sequence','>',0)])
+ if stage_id:
+ self.stage_set(cr, uid, [lead.id], stage_id)
+ return res
- def case_close(self, cr, uid, ids, *args):
- """Overrides close for crm_case for setting close date
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of case Ids
- @param *args: Tuple Value for additional Params
- """
- res = super(crm_lead, self).case_close(cr, uid, ids, *args)
+ def case_close(self, cr, uid, ids, context=None):
+ res = super(crm_lead, self).case_close(cr, uid, ids, context)
self.write(cr, uid, ids, {'date_closed': time.strftime('%Y-%m-%d %H:%M:%S')})
- for case in self.browse(cr, uid, ids):
- if case.type == 'lead':
- message = _("The lead '%s' has been closed.") % case.name
- elif case.type == 'opportunity':
- message = _("The opportunity '%s' has been closed.") % case.name
- else:
- message = _("The case '%s' has been closed.") % case.name
- self.log(cr, uid, case.id, message)
return res
- def convert_opportunity(self, cr, uid, ids, context=None):
- """ Precomputation for converting lead to opportunity
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of closeday’s IDs
- @param context: A standard dictionary for contextual values
- @return: Value of action in dict
+ def case_cancel(self, cr, uid, ids, context=None):
+ """Overrides cancel for crm_case for setting probability
+ """
+ res = super(crm_lead, self).case_cancel(cr, uid, ids, context)
+ self.write(cr, uid, ids, {'probability' : 0.0})
+ return res
+
+ def case_reset(self, cr, uid, ids, context=None):
+ """Overrides reset as draft in order to set the stage field as empty
+ """
+ res = super(crm_lead, self).case_reset(cr, uid, ids, context)
+ self.write(cr, uid, ids, {'stage_id': False, 'probability': 0.0})
+ return res
+
+ def case_mark_lost(self, cr, uid, ids, context=None):
+ """Mark the case as lost: state = done and probability = 0%
+ """
+ res = super(crm_lead, self).case_close(cr, uid, ids, context)
+ self.write(cr, uid, ids, {'probability' : 0.0})
+ for lead in self.browse(cr, uid, ids):
+ stage_id = self.stage_find_lost(cr, uid, lead.section_id.id or False)
+ if stage_id:
+ self.stage_set(cr, uid, [lead.id], stage_id)
+ return res
+
+ def case_mark_won(self, cr, uid, ids, context=None):
+ """Mark the case as lost: state = done and probability = 0%
+ """
+ res = super(crm_lead, self).case_close(cr, uid, ids, context=None)
+ self.write(cr, uid, ids, {'probability' : 100.0})
+ for lead in self.browse(cr, uid, ids):
+ stage_id = self.stage_find_won(cr, uid, lead.section_id.id or False)
+ if stage_id:
+ self.stage_set(cr, uid, [lead.id], stage_id)
+ self._case_mark_won_notification(lead, context=context)
+ return res
+
+ def set_priority(self, cr, uid, ids, priority):
+ """Set lead priority
+ """
+ return self.write(cr, uid, ids, {'priority' : priority})
+
+ def set_high_priority(self, cr, uid, ids, context=None):
+ """Set lead priority to high
"""
+ return self.set_priority(cr, uid, ids, '1')
+
+ def set_normal_priority(self, cr, uid, ids, context=None):
+ """Set lead priority to normal
+ """
+ return self.set_priority(cr, uid, ids, '3')
+
+
+ def _merge_data(self, cr, uid, ids, oldest, fields, context=None):
+ # prepare opportunity data into dictionary for merging
+ opportunities = self.browse(cr, uid, ids, context=context)
+ def _get_first_not_null(attr):
+ if hasattr(oldest, attr):
+ return getattr(oldest, attr)
+ for opportunity in opportunities:
+ if hasattr(opportunity, attr):
+ return getattr(opportunity, attr)
+ return False
+
+ def _get_first_not_null_id(attr):
+ res = _get_first_not_null(attr)
+ return res and res.id or False
+
+ def _concat_all(attr):
+ return ', '.join(filter(lambda x: x, [getattr(opportunity, attr) or '' for opportunity in opportunities if hasattr(opportunity, attr)]))
+
+ data = {}
+ for field_name in fields:
+ field_info = self._all_columns.get(field_name)
+ if field_info is None:
+ continue
+ field = field_info.column
+ if field._type in ('many2many', 'one2many'):
+ continue
+ elif field._type == 'many2one':
+ data[field_name] = _get_first_not_null_id(field_name) # !!
+ elif field._type == 'text':
+ data[field_name] = _concat_all(field_name) #not lost
+ else:
+ data[field_name] = _get_first_not_null(field_name) #not lost
+ return data
+
+ def _merge_find_oldest(self, cr, uid, ids, context=None):
if context is None:
context = {}
- context.update({'active_ids': ids})
+ #TOCHECK: where pass 'convert' in context ?
+ if context.get('convert'):
+ ids = list(set(ids) - set(context.get('lead_ids', False)) )
+
+ #search opportunities order by create date
+ opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date' , context=context)
+ oldest_id = opportunity_ids[0]
+ return self.browse(cr, uid, oldest_id, context=context)
+
+ def _mail_body_text(self, cr, uid, lead, fields, title=False, context=None):
+ body = []
+ if title:
+ body.append("%s\n" % (title))
+ for field_name in fields:
+ field_info = self._all_columns.get(field_name)
+ if field_info is None:
+ continue
+ field = field_info.column
+ value = None
+
+ if field._type == 'selection':
+ if hasattr(field.selection, '__call__'):
+ key = field.selection(self, cr, uid, context=context)
+ else:
+ key = field.selection
+ value = dict(key).get(lead[field_name], lead[field_name])
+ elif field._type == 'many2one':
+ if lead[field_name]:
+ value = lead[field_name].name_get()[0][1]
+ else:
+ value = lead[field_name]
+
+ body.append("%s: %s" % (field.string, value or ''))
+ return "\n".join(body + ['---'])
+
+ def _merge_notification(self, cr, uid, opportunity_id, opportunities, context=None):
+ #TOFIX: mail template should be used instead of fix body, subject text
+ details = []
+ merge_message = _('Merged opportunities')
+ subject = [merge_message]
+ fields = ['name', 'partner_id', 'stage_id', 'section_id', 'user_id', 'categ_id', 'channel_id', 'company_id', 'contact_name',
+ 'email_from', 'phone', 'fax', 'mobile', 'state_id', 'description', 'probability', 'planned_revenue',
+ 'country_id', 'city', 'street', 'street2', 'zip']
+ for opportunity in opportunities:
+ subject.append(opportunity.name)
+ title = "%s : %s" % (merge_message, opportunity.name)
+ details.append(self._mail_body_text(cr, uid, opportunity, fields, title=title, context=context))
+
+ subject = subject[0] + ", ".join(subject[1:])
+ details = "\n\n".join(details)
+ return opportunity.message_append_note(subject, body=details, need_action_user_id=opportunity.user_id.id)
+
+ def _merge_opportunity_history(self, cr, uid, opportunity_id, opportunities, context=None):
+ message = self.pool.get('mail.message')
+ for opportunity in opportunities:
+ for history in opportunity.message_ids:
+ message.write(cr, uid, history.id, {
+ 'res_id': opportunity_id,
+ 'subject' : _("From %s : %s") % (opportunity.name, history.subject)
+ }, context=context)
+
+ return True
+
+ def _merge_opportunity_attachments(self, cr, uid, opportunity_id, opportunities, context=None):
+ attachment = self.pool.get('ir.attachment')
+
+ # return attachments of opportunity
+ def _get_attachments(opportunity_id):
+ attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', opportunity_id)], context=context)
+ return attachment.browse(cr, uid, attachment_ids, context=context)
+
+ count = 1
+ first_attachments = _get_attachments(opportunity_id)
+ for opportunity in opportunities:
+ attachments = _get_attachments(opportunity.id)
+ for first in first_attachments:
+ for attachment in attachments:
+ if attachment.name == first.name:
+ values = dict(
+ name = "%s (%s)" % (attachment.name, count,),
+ res_id = opportunity_id,
+ )
+ attachment.write(values)
+ count+=1
- data_obj = self.pool.get('ir.model.data')
- value = {}
+ return True
+ def merge_opportunity(self, cr, uid, ids, context=None):
+ """
+ To merge opportunities
+ :param ids: list of opportunities ids to merge
+ """
+ if context is None: context = {}
- for case in self.browse(cr, uid, ids, context=context):
- context.update({'active_id': case.id})
- data_id = data_obj._get_id(cr, uid, 'crm', 'view_crm_lead2opportunity_partner')
- view_id1 = False
- if data_id:
- view_id1 = data_obj.browse(cr, uid, data_id, context=context).res_id
- value = {
- 'name': _('Create Partner'),
- 'view_type': 'form',
- 'view_mode': 'form,tree',
- 'res_model': 'crm.lead2opportunity.partner',
- 'view_id': False,
- 'context': context,
- 'views': [(view_id1, 'form')],
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- 'nodestroy': True
- }
- return value
+ #TOCHECK: where pass lead_ids in context?
+ lead_ids = context and context.get('lead_ids', []) or []
- def write(self, cr, uid, ids, vals, context=None):
- if not context:
- context = {}
+ if len(ids) <= 1:
+ raise osv.except_osv(_('Warning !'),_('Please select more than one opportunity from the list view.'))
- if 'date_closed' in vals:
- return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
+ ctx_opportunities = self.browse(cr, uid, lead_ids, context=context)
+ opportunities = self.browse(cr, uid, ids, context=context)
+ opportunities_list = list(set(opportunities) - set(ctx_opportunities))
+ oldest = self._merge_find_oldest(cr, uid, ids, context=context)
+ if ctx_opportunities :
+ first_opportunity = ctx_opportunities[0]
+ tail_opportunities = opportunities_list
+ else:
+ first_opportunity = opportunities_list[0]
+ tail_opportunities = opportunities_list[1:]
+
+ fields = ['partner_id', 'title', 'name', 'categ_id', 'channel_id', 'city', 'company_id', 'contact_name', 'country_id',
+ 'partner_address_id', 'type_id', 'user_id', 'section_id', 'state_id', 'description', 'email', 'fax', 'mobile',
+ 'partner_name', 'phone', 'probability', 'planned_revenue', 'street', 'street2', 'zip', 'create_date', 'date_action_last',
+ 'date_action_next', 'email_from', 'email_cc', 'partner_name']
+
+ data = self._merge_data(cr, uid, ids, oldest, fields, context=context)
+
+ # merge data into first opportunity
+ self.write(cr, uid, [first_opportunity.id], data, context=context)
+
+ #copy message and attachements into the first opportunity
+ self._merge_opportunity_history(cr, uid, first_opportunity.id, tail_opportunities, context=context)
+ self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context)
+
+ #Notification about loss of information
+ self._merge_notification(cr, uid, first_opportunity, opportunities, context=context)
+ #delete tail opportunities
+ self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context)
+
+ #open first opportunity
+ self.case_open(cr, uid, [first_opportunity.id])
+ return first_opportunity.id
+
+ def _convert_opportunity_data(self, cr, uid, lead, customer, section_id=False, context=None):
+ crm_stage = self.pool.get('crm.case.stage')
+ contact_id = False
+ if customer:
+ contact_id = self.pool.get('res.partner').address_get(cr, uid, [customer.id])['default']
+ if not section_id:
+ section_id = lead.section_id and lead.section_id.id or False
+ if section_id:
+ stage_ids = crm_stage.search(cr, uid, [('sequence','>=',1), ('section_ids','=', section_id)])
+ else:
+ stage_ids = crm_stage.search(cr, uid, [('sequence','>=',1)])
+ stage_id = stage_ids and stage_ids[0] or False
+ return {
+ 'planned_revenue': lead.planned_revenue,
+ 'probability': lead.probability,
+ 'name': lead.name,
+ 'partner_id': customer and customer.id or False,
+ 'user_id': (lead.user_id and lead.user_id.id),
+ 'type': 'opportunity',
+ 'stage_id': stage_id or False,
+ 'date_action': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_open': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'partner_address_id': contact_id,
+ }
- if 'stage_id' in vals and vals['stage_id']:
- stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
- text = _("Changed Stage to: %s") % stage_obj.name
- self.message_append(cr, uid, ids, text, body_text=text, context=context)
- message=''
- for case in self.browse(cr, uid, ids, context=context):
- if case.type == 'lead' or context.get('stage_type',False)=='lead':
- message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
- elif case.type == 'opportunity':
- message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
- self.log(cr, uid, case.id, message)
- return super(crm_lead,self).write(cr, uid, ids, vals, context)
+ def _convert_opportunity_notification(self, cr, uid, lead, context=None):
+ success_message = _("Lead is <b>converted to an opportunity</b>.")
+ lead.message_append_note(success_message ,success_message, need_action_user_id=lead.user_id.id)
+ return True
+
+ def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None):
+ partner = self.pool.get('res.partner')
+ mail_message = self.pool.get('mail.message')
+ customer = False
+ if partner_id:
+ customer = partner.browse(cr, uid, partner_id, context=context)
+ for lead in self.browse(cr, uid, ids, context=context):
+ if lead.state in ('done', 'cancel'):
+ continue
+ if user_ids or section_id:
+ self.allocate_salesman(cr, uid, [lead.id], user_ids, section_id, context=context)
+
+ vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
+ self.write(cr, uid, [lead.id], vals, context=context)
+
+ self._convert_opportunity_notification(cr, uid, lead, context=context)
+ #TOCHECK: why need to change partner details in all messages of lead ?
+ if lead.partner_id:
+ msg_ids = [ x.id for x in lead.message_ids]
+ mail_message.write(cr, uid, msg_ids, {
+ 'partner_id': lead.partner_id.id
+ }, context=context)
+ return True
+
+ def _lead_create_partner(self, cr, uid, lead, context=None):
+ partner = self.pool.get('res.partner')
+ partner_id = partner.create(cr, uid, {
+ 'name': lead.partner_name or lead.contact_name or lead.name,
+ 'user_id': lead.user_id.id,
+ 'comment': lead.description,
+ 'section_id': lead.section_id.id or False,
+ 'address': []
+ })
+ return partner_id
+
+ def _lead_set_partner(self, cr, uid, lead, partner_id, context=None):
+ res = False
+ res_partner = self.pool.get('res.partner')
+ if partner_id:
+ res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id.id or False})
+ contact_id = res_partner.address_get(cr, uid, [partner_id])['default']
+ res = lead.write({'partner_id' : partner_id, 'partner_address_id': contact_id}, context=context)
- def stage_next(self, cr, uid, ids, context=None):
- stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
- if stage:
- stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
- if stage_obj.on_change:
- data = {'probability': stage_obj.probability}
- self.write(cr, uid, ids, data)
- return stage
-
- def stage_previous(self, cr, uid, ids, context=None):
- stage = super(crm_lead, self).stage_previous(cr, uid, ids, context=context)
- if stage:
- stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
- if stage_obj.on_change:
- data = {'probability': stage_obj.probability}
- self.write(cr, uid, ids, data)
- return stage
+ return res
+
+ def _lead_create_partner_address(self, cr, uid, lead, partner_id, context=None):
+ address = self.pool.get('res.partner.address')
+ return address.create(cr, uid, {
+ 'partner_id': partner_id,
+ 'name': lead.contact_name,
+ 'phone': lead.phone,
+ 'mobile': lead.mobile,
+ 'email': lead.email_from and to_email(lead.email_from)[0],
+ 'fax': lead.fax,
+ 'title': lead.title and lead.title.id or False,
+ 'function': lead.function,
+ 'street': lead.street,
+ 'street2': lead.street2,
+ 'zip': lead.zip,
+ 'city': lead.city,
+ 'country_id': lead.country_id and lead.country_id.id or False,
+ 'state_id': lead.state_id and lead.state_id.id or False,
+ })
+
+ def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None):
+ """
+ This function convert partner based on action.
+ if action is 'create', create new partner with contact and assign lead to new partner_id.
+ otherwise assign lead to specified partner_id
+ """
+ if context is None:
+ context = {}
+ partner_ids = {}
+ for lead in self.browse(cr, uid, ids, context=context):
+ if action == 'create':
+ if not partner_id:
+ partner_id = self._lead_create_partner(cr, uid, lead, context=context)
+ self._lead_create_partner_address(cr, uid, lead, partner_id, context=context)
+ self._lead_set_partner(cr, uid, lead, partner_id, context=context)
+ partner_ids[lead.id] = partner_id
+ return partner_ids
+
+ def _send_mail_to_salesman(self, cr, uid, lead, context=None):
+ """
+ Send mail to salesman with updated Lead details.
+ @ lead: browse record of 'crm.lead' object.
+ """
+ #TOFIX: mail template should be used here instead of fix subject, body text.
+ message = self.pool.get('mail.message')
+ email_to = lead.user_id and lead.user_id.user_email
+ if not email_to:
+ return False
+
+ email_from = lead.section_id and lead.section_id.user_id and lead.section_id.user_id.user_email or email_to
+ partner = lead.partner_id and lead.partner_id.name or lead.partner_name
+ subject = "lead %s converted into opportunity" % lead.name
+ body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)
+ return message.schedule_with_attach(cr, uid, email_from, [email_to], subject, body)
+
+
+ def allocate_salesman(self, cr, uid, ids, user_ids, team_id=False, context=None):
+ index = 0
+ for lead_id in ids:
+ value = {}
+ if team_id:
+ value['section_id'] = team_id
+ if index < len(user_ids):
+ value['user_id'] = user_ids[index]
+ index += 1
+ if value:
+ self.write(cr, uid, [lead_id], value, context=context)
+ return True
+
+ def schedule_phonecall(self, cr, uid, ids, schedule_time, call_summary, desc, phone, contact_name, user_id=False, section_id=False, categ_id=False, action='schedule', context=None):
+ """
+ action :('schedule','Schedule a call'), ('log','Log a call')
+ """
+ phonecall = self.pool.get('crm.phonecall')
+ model_data = self.pool.get('ir.model.data')
+ phonecall_dict = {}
+ if not categ_id:
+ res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2')
+ if res_id:
+ categ_id = model_data.browse(cr, uid, res_id, context=context).res_id
+ for lead in self.browse(cr, uid, ids, context=context):
+ if not section_id:
+ section_id = lead.section_id and lead.section_id.id or False
+ if not user_id:
+ user_id = lead.user_id and lead.user_id.id or False
+ vals = {
+ 'name' : call_summary,
+ 'opportunity_id' : lead.id,
+ 'user_id' : user_id or False,
+ 'categ_id' : categ_id or False,
+ 'description' : desc or '',
+ 'date' : schedule_time,
+ 'section_id' : section_id or False,
+ 'partner_id': lead.partner_id and lead.partner_id.id or False,
+ 'partner_address_id': lead.partner_address_id and lead.partner_address_id.id or False,
+ 'partner_phone' : phone or lead.phone or (lead.partner_address_id and lead.partner_address_id.phone or False),
+ 'partner_mobile' : lead.partner_address_id and lead.partner_address_id.mobile or False,
+ 'priority': lead.priority,
+ }
+
+ new_id = phonecall.create(cr, uid, vals, context=context)
+ phonecall.case_open(cr, uid, [new_id])
+ if action == 'log':
+ phonecall.case_close(cr, uid, [new_id])
+ phonecall_dict[lead.id] = new_id
+ self._case_phonecall_notification(cr, uid, [new_id], lead, phonecall, action, context=context)
+ return phonecall_dict
+
+
+ def redirect_opportunity_view(self, cr, uid, opportunity_id, context=None):
+ models_data = self.pool.get('ir.model.data')
+
+ # Get Opportunity views
+ form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
+ tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_oppor')
+ return {
+ 'name': _('Opportunity'),
+ 'view_type': 'form',
+ 'view_mode': 'tree, form',
+ 'res_model': 'crm.lead',
+ 'domain': [('type', '=', 'opportunity')],
+ 'res_id': int(opportunity_id),
+ 'view_id': False,
+ 'views': [(form_view and form_view[1] or False, 'form'),
+ (tree_view and tree_view[1] or False, 'tree'),
+ (False, 'calendar'), (False, 'graph')],
+ 'type': 'ir.actions.act_window',
+ }
- def unlink(self, cr, uid, ids, context=None):
- for lead in self.browse(cr, uid, ids, context):
- if (not lead.section_id.allow_unlink) and (lead.state <> 'draft'):
- raise osv.except_osv(_('Warning !'),
- _('You can not delete this lead. You should better cancel it.'))
- return super(crm_lead, self).unlink(cr, uid, ids, context)
def message_new(self, cr, uid, msg, custom_values=None, context=None):
"""Automatically calls when new email message arrives"""
res_id = super(crm_lead, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
subject = msg.get('subject') or _("No Subject")
body = msg.get('body_text')
+
msg_from = msg.get('from')
priority = msg.get('priority')
vals = {
if priority:
vals['priority'] = priority
vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False)))
- res_id = self.write(cr, uid, [res_id], vals, context)
+ self.write(cr, uid, [res_id], vals, context)
return res_id
- def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None):
+ def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None):
if isinstance(ids, (str, int, long)):
ids = [ids]
-
- super(crm_lead, self).message_update(cr, uid, msg,
- custom_values=custom_values,
- context=context)
+ if vals == None:
+ vals = {}
+ super(crm_lead, self).message_update(cr, uid, ids, msg, context=context)
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
vals['priority'] = msg.get('priority')
for case in self.browse(cr, uid, ids, context=context):
values = dict(vals)
if case.state in CRM_LEAD_PENDING_STATES:
- values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open
+ #re-open
+ values.update(state=crm.AVAILABLE_STATES[1][0])
+ if not case.date_open:
+ values['date_open'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
res = self.write(cr, uid, [case.id], values, context=context)
return res
- def on_change_optin(self, cr, uid, ids, optin):
- return {'value':{'optin':optin,'optout':False}}
+ def action_makeMeeting(self, cr, uid, ids, context=None):
+ """
+ This opens Meeting's calendar view to schedule meeting on current Opportunity
+ @return : Dictionary value for created Meeting view
+ """
+ if context is None:
+ context = {}
+ value = {}
+ data_obj = self.pool.get('ir.model.data')
+ for opp in self.browse(cr, uid, ids, context=context):
+ # Get meeting views
+ tree_view = data_obj.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_meet')
+ form_view = data_obj.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_meet')
+ calander_view = data_obj.get_object_reference(cr, uid, 'crm', 'crm_case_calendar_view_meet')
+ search_view = data_obj.get_object_reference(cr, uid, 'crm', 'view_crm_case_meetings_filter')
+ context.update({
+ 'default_opportunity_id': opp.id,
+ 'default_partner_id': opp.partner_id and opp.partner_id.id or False,
+ 'default_user_id': uid,
+ 'default_section_id': opp.section_id and opp.section_id.id or False,
+ 'default_email_from': opp.email_from,
+ 'default_state': 'open',
+ 'default_name': opp.name
+ })
+ value = {
+ 'name': _('Meetings'),
+ 'context': context,
+ 'view_type': 'form',
+ 'view_mode': 'calendar,form,tree',
+ 'res_model': 'crm.meeting',
+ 'view_id': False,
+ 'views': [(calander_view and calander_view[1] or False, 'calendar'), (form_view and form_view[1] or False, 'form'), (tree_view and tree_view[1] or False, 'tree')],
+ 'type': 'ir.actions.act_window',
+ 'search_view_id': search_view and search_view[1] or False,
+ 'nodestroy': True
+ }
+ return value
- def on_change_optout(self, cr, uid, ids, optout):
- return {'value':{'optout':optout,'optin':False}}
+
+ def unlink(self, cr, uid, ids, context=None):
+ for lead in self.browse(cr, uid, ids, context):
+ if (not lead.section_id.allow_unlink) and (lead.state != 'draft'):
+ raise osv.except_osv(_('Error'),
+ _("You cannot delete lead '%s'; it must be in state 'Draft' to be deleted. " \
+ "You should better cancel it, instead of deleting it.") % lead.name)
+ return super(crm_lead, self).unlink(cr, uid, ids, context)
+
+
+ def write(self, cr, uid, ids, vals, context=None):
+ if not context:
+ context = {}
+
+ if 'date_closed' in vals:
+ return super(crm_lead,self).write(cr, uid, ids, vals, context=context)
+
+ if vals.get('stage_id'):
+ stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
+ # change probability of lead(s) if required by stage
+ if not vals.get('probability') and stage.on_change:
+ vals['probability'] = stage.probability
+ text = _("Changed Stage to: %s") % stage.name
+
+ for case in self.browse(cr, uid, ids, context=context):
+ if case.type == 'lead' or context.get('stage_type') == 'lead':
+ message = _("The stage of lead has been changed to <b>%s</b>.") % (stage.name)
+ case.message_append_note(text, message)
+ elif case.type == 'opportunity':
+ message = _("The stage of opportunity has been changed to <b>%s</b>.") % (stage.name)
+ case.message_append_note(text, message)
+
+ return super(crm_lead,self).write(cr, uid, ids, vals, context)
crm_lead()