merge upstream
authorChristophe Simonis <chs@openerp.com>
Wed, 17 Jul 2013 14:29:47 +0000 (16:29 +0200)
committerChristophe Simonis <chs@openerp.com>
Wed, 17 Jul 2013 14:29:47 +0000 (16:29 +0200)
bzr revid: chs@openerp.com-20130717142947-de40mn3234gt5044

1  2 
addons/crm/crm_lead.py
addons/crm/wizard/crm_lead_to_opportunity.py
addons/crm/wizard/crm_lead_to_opportunity_view.xml
addons/crm/wizard/crm_merge_opportunities_view.xml

diff --combined addons/crm/crm_lead.py
  #
  ##############################################################################
  
  import crm
  from datetime import datetime
  from operator import itemgetter
- from openerp.osv import fields, osv
+ from openerp.osv import fields, osv, orm
  import time
+ from openerp import SUPERUSER_ID
  from openerp import tools
  from openerp.tools.translate import _
  from openerp.tools import html2plaintext
@@@ -67,7 -67,7 +67,7 @@@ CRM_LEAD_PENDING_STATES = 
      crm.AVAILABLE_STATES[4][0], # Pending
  )
  
- class crm_lead(base_stage, format_address, osv.osv):
+ class crm_lead(format_address, osv.osv):
      """ CRM Lead Case """
      _name = "crm.lead"
      _description = "Lead/Opportunity"
  
      _track = {
          'state': {
-             'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'new',
-             'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'done',
-             'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
+             'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
+             'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
+             'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.state == 'cancel',
          },
          'stage_id': {
-             'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj['state'] not in ['new', 'cancel', 'done'],
+             'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'cancel', 'done'],
          },
      }
  
          context['empty_list_help_document_name'] = _("leads")
          return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
  
-     def create(self, cr, uid, vals, context=None):
-         if context is None:
-             context = {}
-         if not vals.get('stage_id'):
-             ctx = context.copy()
-             if vals.get('section_id'):
-                 ctx['default_section_id'] = vals['section_id']
-             if vals.get('type'):
-                 ctx['default_type'] = vals['type']
-             vals['stage_id'] = self._get_default_stage_id(cr, uid, context=ctx)
-         return super(crm_lead, self).create(cr, uid, vals, context=context)
      def _get_default_section_id(self, cr, uid, context=None):
          """ Gives default section by checking if present in the context """
          return self._resolve_section_id_from_context(cr, uid, context=context) or False
          if type(context.get('default_section_id')) in (int, long):
              return context.get('default_section_id')
          if isinstance(context.get('default_section_id'), basestring):
-             section_name = context['default_section_id']
-             section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=section_name, context=context)
+             section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=context['default_section_id'], context=context)
              if len(section_ids) == 1:
                  return int(section_ids[0][0])
          return None
          'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
          'date_closed': fields.datetime('Closed', readonly=True),
          'stage_id': fields.many2one('crm.case.stage', 'Stage', track_visibility='onchange',
-                         domain="['&', '&', ('fold', '=', False), ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
+                         domain="['&', ('section_ids', '=', section_id), '|', ('type', '=', type), ('type', '=', 'both')]"),
          'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'),
          'referred': fields.char('Referred By', size=64),
          'date_open': fields.datetime('Opened', readonly=True),
      _defaults = {
          'active': 1,
          'type': 'lead',
-         'user_id': lambda s, cr, uid, c: s._get_default_user(cr, uid, c),
-         'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
+         'user_id': lambda s, cr, uid, c: uid,
          'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
          'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
          'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
  
      def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
          if not stage_id:
-             return {'value':{}}
+             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}}
+             return {'value': {}}
+         return {'value': {'probability': stage.probability}}
  
-     def on_change_partner(self, cr, uid, ids, partner_id, context=None):
-         result = {}
+     def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
          values = {}
          if partner_id:
              partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
              values = {
-                 'partner_name' : partner.name,
-                 'street' : partner.street,
-                 'street2' : partner.street2,
-                 'city' : partner.city,
-                 'state_id' : partner.state_id and partner.state_id.id or False,
-                 'country_id' : partner.country_id and partner.country_id.id or False,
-                 'email_from' : partner.email,
-                 'phone' : partner.phone,
-                 'mobile' : partner.mobile,
-                 'fax' : partner.fax,
+                 'partner_name': partner.name,
+                 'street': partner.street,
+                 'street2': partner.street2,
+                 'city': partner.city,
+                 'state_id': partner.state_id and partner.state_id.id or False,
+                 'country_id': partner.country_id and partner.country_id.id or False,
+                 'email_from': partner.email,
+                 'phone': partner.phone,
+                 'mobile': partner.mobile,
+                 'fax': partner.fax,
              }
-         return {'value' : values}
+         return {'value': values}
  
      def on_change_user(self, cr, uid, ids, user_id, context=None):
          """ When changing the user, also set a section_id or restrict section id
              to the ones user_id is member of. """
-         section_id = False
-         if user_id:
+         section_id = self._get_default_section_id(cr, uid, context=context) or False
+         if user_id and not section_id:
              section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
              if section_ids:
                  section_id = section_ids[0]
          # collect all section_ids
          section_ids = []
          types = ['both']
-         if not cases :
+         if not cases:
              type = context.get('default_type')
              types += [type]
          if section_id:
              return stage_ids[0]
          return False
  
-     def case_cancel(self, cr, uid, ids, context=None):
-         """ Overrides case_cancel from base_stage to set probability """
-         res = super(crm_lead, self).case_cancel(cr, uid, ids, context=context)
-         self.write(cr, uid, ids, {'probability' : 0.0}, context=context)
-         return res
-     def case_reset(self, cr, uid, ids, context=None):
-         """ Overrides case_reset from base_stage to set probability """
-         res = super(crm_lead, self).case_reset(cr, uid, ids, context=context)
-         self.write(cr, uid, ids, {'probability': 0.0}, context=context)
-         return res
+     def stage_set(self, cr, uid, ids, stage_id, context=None):
+         """ Set the new stage. Now just writes the stage.
+             TDE TODO: remove me when removing state
+         """
+         return self.write(cr, uid, ids, {'stage_id': stage_id}, context=context)
  
      def case_mark_lost(self, cr, uid, ids, context=None):
          """ Mark the case as lost: state=cancel and probability=0 """
          for lead in self.browse(cr, uid, ids):
              stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0),('on_change','=',True)], context=context)
              if stage_id:
-                 self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context)
+                 self.stage_set(cr, uid, [lead.id], stage_id, context=context)
          return True
  
      def case_mark_won(self, cr, uid, ids, context=None):
          for lead in self.browse(cr, uid, ids):
              stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0),('on_change','=',True)], context=context)
              if stage_id:
-                 self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context)
+                 self.stage_set(cr, uid, [lead.id], stage_id, context=context)
+         return True
+     def case_escalate(self, cr, uid, ids, context=None):
+         """ Escalates case to parent level """
+         for case in self.browse(cr, uid, ids, context=context):
+             data = {'active': True}
+             if case.section_id.parent_id:
+                 data['section_id'] = case.section_id.parent_id.id
+                 if case.section_id.parent_id.change_responsible:
+                     if case.section_id.parent_id.user_id:
+                         data['user_id'] = case.section_id.parent_id.user_id.id
+             else:
+                 raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
+             self.write(cr, uid, [case.id], data, context=context)
          return True
  
      def set_priority(self, cr, uid, ids, priority):
                  attachment.write(values)
          return True
  
 -    def merge_opportunity(self, cr, uid, ids, context=None):
 +    def merge_opportunity(self, cr, uid, ids, user_id=False, section_id=False, context=None):
          """
          Different cases of merge:
          - merge leads together = 1 new lead
          fields = list(CRM_LEAD_FIELDS_TO_MERGE)
          merged_data = self._merge_data(cr, uid, ids, highest, fields, context=context)
  
 +        if user_id:
 +            merged_data['user_id'] = user_id
 +        if section_id:
 +            merged_data['section_id'] = section_id
 +
          # Merge messages and attachements into the first opportunity
          self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context)
          self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context)
                  merged_data['stage_id'] = section_stage_ids and section_stage_ids[0] or False
          # Write merged data into first opportunity
          self.write(cr, uid, [highest.id], merged_data, context=context)
-         # Delete tail opportunities
-         self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context)
+         # Delete tail opportunities 
+         # We use the SUPERUSER to avoid access rights issues because as the user had the rights to see the records it should be safe to do so
+         self.unlink(cr, SUPERUSER_ID, [x.id for x in tail_opportunities], context=context)
  
          return highest.id
  
                  continue
              vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
              self.write(cr, uid, [lead.id], vals, context=context)
-         self.message_post(cr, uid, ids, body=_("Lead <b>converted into an Opportunity</b>"), subtype="crm.mt_lead_convert_to_opportunity", context=context)
  
          if user_ids or section_id:
              self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
          }
          return res
  
+     def create(self, cr, uid, vals, context=None):
+         if context is None:
+             context = {}
+         if vals.get('type') and not context.get('default_type'):
+             context['default_type'] = vals.get('type')
+         if vals.get('section_id') and not context.get('default_section_id'):
+             context['default_section_id'] = vals.get('section_id')
+         # context: no_log, because subtype already handle this
+         create_context = dict(context, mail_create_nolog=True)
+         return super(crm_lead, self).create(cr, uid, vals, context=create_context)
      def write(self, cr, uid, ids, vals, context=None):
          if vals.get('stage_id') and not vals.get('probability'):
-             # change probability of lead(s) if required by stage
-             stage = self.pool.get('crm.case.stage').browse(cr, uid, vals['stage_id'], context=context)
-             if stage.on_change:
-                 vals['probability'] = stage.probability
+             onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']
+             vals.update(onchange_stage_values)
          return super(crm_lead, self).write(cr, uid, ids, vals, context=context)
  
      def new_mail_send(self, cr, uid, ids, context=None):
      def message_get_reply_to(self, cr, uid, ids, context=None):
          """ Override to get the reply_to of the parent project. """
          return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
-                     for lead in self.browse(cr, uid, ids, context=context)]
+                     for lead in self.browse(cr, SUPERUSER_ID, ids, context=context)]
+     def _get_formview_action(self, cr, uid, id, context=None):
+         action = super(crm_lead, self)._get_formview_action(cr, uid, id, context=context)
+         obj = self.browse(cr, uid, id, context=context)
+         if obj.type == 'opportunity':
+             model, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor')
+             action.update({
+                 'views': [(view_id, 'form')],
+                 })
+         return action
  
      def message_get_suggested_recipients(self, cr, uid, ids, context=None):
          recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
-         for lead in self.browse(cr, uid, ids, context=context):
-             if lead.partner_id:
-                 self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
-             elif lead.email_from:
-                 self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+         try:
+             for lead in self.browse(cr, uid, ids, context=context):
+                 if lead.partner_id:
+                     self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
+                 elif lead.email_from:
+                     self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+         except (osv.except_osv, orm.except_orm):  # no read access rights -> just ignore suggested recipients because this imply modifying followers
+             pass
          return recipients
  
      def message_new(self, cr, uid, msg, custom_values=None, context=None):
              'user_id': False,
          }
          if msg.get('author_id'):
-             defaults.update(self.on_change_partner(cr, uid, None, msg.get('author_id'), context=context)['value'])
+             defaults.update(self.on_change_partner_id(cr, uid, None, msg.get('author_id'), context=context)['value'])
          if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
              defaults['priority'] = msg.get('priority')
          defaults.update(custom_values)
          message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix)
          return self.message_post(cr, uid, ids, body=message, context=context)
  
+     def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None):
+         if not duration:
+             duration = _('unknown')
+         else:
+             duration = str(duration)
+         message = _("Meeting scheduled at '%s'<br> Subject: %s <br> Duration: %s hour(s)") % (meeting_date, meeting_subject, duration)
+         return self.message_post(cr, uid, ids, body=message, context=context)
      def onchange_state(self, cr, uid, ids, state_id, context=None):
          if state_id:
              country_id=self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id
@@@ -35,8 -35,6 +35,8 @@@ class crm_lead2opportunity_partner(osv.
                  ('merge', 'Merge with existing opportunities')
              ], 'Conversion Action', required=True),
          'opportunity_ids': fields.many2many('crm.lead', string='Opportunities'),
 +        'user_id': fields.many2one('res.users', 'Salesperson', select=True),
 +        'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True),
      }
  
      def default_get(self, cr, uid, fields, context=None):
                  res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
              if 'opportunity_ids' in fields and len(tomerge) >= 2:
                  res.update({'opportunity_ids': list(tomerge)})
 -
 +            if lead.user_id:
 +                res.update({'user_id': lead.user_id.id})
 +            if lead.section_id:
 +                res.update({'section_id': lead.section_id.id})
          return res
  
 +    def on_change_user(self, cr, uid, ids, user_id, section_id, context=None):
 +        """ When changing the user, also set a section_id or restrict section id
 +            to the ones user_id is member of. """
 +        if user_id:
 +            if section_id:
 +                user_in_section = self.pool.get('crm.case.section').search(cr, uid, [('id', '=', section_id), '|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context, count=True)
 +            else:
 +                user_in_section = False
 +            if not user_in_section:
 +                section_id = False
 +                section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
 +                if section_ids:
 +                    section_id = section_ids[0]
 +        return {'value': {'section_id': section_id}}
 +
      def view_init(self, cr, uid, fields, context=None):
          """
          Check some preconditions before the wizard executes.
          lead_obj = self.pool.get('crm.lead')
          for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context):
              if lead.state in ['done', 'cancel']:
-                 raise osv.except_osv(_("Warning !"), _("Closed/Cancelled leads cannot be converted into opportunities."))
+                 raise osv.except_osv(_("Warning!"), _("Closed/Cancelled leads cannot be converted into opportunities."))
          return False
  
      def _convert_opportunity(self, cr, uid, ids, vals, context=None):
          w = self.browse(cr, uid, ids, context=context)[0]
          opp_ids = [o.id for o in w.opportunity_ids]
          if w.name == 'merge':
 -            lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, context=context)
 +            lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, w.user_id.id, w.section_id.id, context=context)
              lead_ids = [lead_id]
              lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
              if lead['type'] == "lead":
                  context.update({'active_ids': lead_ids})
 -                self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
 +                self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
          else:
              lead_ids = context.get('active_ids', [])
 -            self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
 +            self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
  
          return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
  
                      <group name="name">
                          <field name="name" class="oe_inline"/>
                      </group>
 +                    <group string="Assign opportunities to">
 +                        <field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
 +                        <field name="section_id" class="oe_inline"/>
 +                    </group>
                      <group string="Opportunities">
                          <field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}" nolabel="1">
                              <tree>
                              attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
                              class="oe_inline"/>
                      </group>
 +                    <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
 +                        <field name="section_id" groups="base.group_multi_salesteams"/>
 +                        <field name="user_ids" widget="many2many_tags"/>
 +                    </group>
                      <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
                          <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
                              <tree>
                              </tree>
                          </field>
                      </group>
 -
 -                    <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
 -                        <field name="section_id" groups="base.group_multi_salesteams"/>
 -                        <field name="user_ids" widget="many2many_tags"/>
 -                    </group>
 -
                      <footer>
                          <button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/>
                          or
              res_model="crm.lead2opportunity.partner.mass" src_model="crm.lead"
              view_mode="form" target="new" view_type="form"
              context="{'mass_convert' : True}"
-             view_id="view_crm_lead2opportunity_partner_mass"/>
+             view_id="view_crm_lead2opportunity_partner_mass"
+             groups="base.group_sale_salesman"
+         />
  
      </data>
  </openerp>
@@@ -8,25 -8,20 +8,25 @@@
              <field name="model">crm.merge.opportunity</field>
              <field name="arch" type="xml">
                  <form string="Merge Leads/Opportunities" version="7.0">
 -                    <separator string="Select Leads/Opportunities"/>
 -                    <field name="opportunity_ids">
 -                        <tree>
 -                            <field name="create_date"/>
 -                            <field name="name"/>
 -                            <field name="type"/>
 -                            <field name="contact_name"/>
 -                            <field name="email_from"/>
 -                            <field name="phone"/>
 -                            <field name="stage_id"/>
 -                            <field name="user_id"/>
 -                            <field name="section_id" groups="base.group_multi_salesteams"/>
 -                        </tree>
 -                    </field>
 +                    <group string="Assign opportunities to">
 +                        <field name="user_id" class="oe_inline" on_change="on_change_user(user_id, context)"/>
 +                        <field name="section_id" class="oe_inline"/>
 +                    </group>
 +                    <group string="Select Leads/Opportunities">
 +                        <field name="opportunity_ids" nolabel="1">
 +                            <tree>
 +                                <field name="create_date"/>
 +                                <field name="name"/>
 +                                <field name="type"/>
 +                                <field name="contact_name"/>
 +                                <field name="email_from"/>
 +                                <field name="phone"/>
 +                                <field name="stage_id"/>
 +                                <field name="user_id"/>
 +                                <field name="section_id" groups="base.group_multi_salesteams"/>
 +                            </tree>
 +                        </field>
 +                    </group>
                      <footer>
                          <button name="action_merge" type="object" string="Merge" class="oe_highlight"/>
                          or
@@@ -50,7 -45,9 +50,9 @@@
              multi="True"
              key2="client_action_multi" name="Merge leads/opportunities"
              res_model="crm.merge.opportunity" src_model="crm.lead"
-             view_mode="form" target="new" view_type="form"/>
+             view_mode="form" target="new" view_type="form"
+             groups="base.group_sale_salesman"
+             />
  
      </data>
  </openerp>