[IMP] crm : various code cleaning
- try to make better use of recordsets (and recordset APIs) - remove pointless decorators - remove redundant ensure_one - make 'stage_find' a private method returning a recordset - etc ...
This commit is contained in:
committed by
Jérome Maes
parent
4f899f17d4
commit
3e2e1d6b90
@@ -11,7 +11,8 @@ from odoo.tools import email_re, email_split
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
|
||||
from odoo.addons.base.res.res_partner import FormatAddress
|
||||
from odoo.addons.crm.models import crm_stage
|
||||
|
||||
from . import crm_stage
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -65,7 +66,7 @@ class Lead(FormatAddress, models.Model):
|
||||
|
||||
def _default_stage_id(self):
|
||||
team = self.env['crm.team'].sudo()._get_default_team_id(user_id=self.env.uid)
|
||||
return self.stage_find(team_id=team.id, domain=[('fold', '=', False)])
|
||||
return self._stage_find(team_id=team.id, domain=[('fold', '=', False)]).id
|
||||
|
||||
name = fields.Char('Opportunity', required=True, index=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', index=True,
|
||||
@@ -91,7 +92,7 @@ class Lead(FormatAddress, models.Model):
|
||||
type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], index=True, required=True,
|
||||
default=lambda self: 'lead' if self.env['res.users'].has_group('crm.group_use_lead') else 'opportunity',
|
||||
help="Type is used to separate Leads and Opportunities")
|
||||
priority = fields.Selection(crm_stage.AVAILABLE_PRIORITIES, string='Rating', index=True, default=lambda *a: crm_stage.AVAILABLE_PRIORITIES[0][0])
|
||||
priority = fields.Selection(crm_stage.AVAILABLE_PRIORITIES, string='Rating', index=True, default=crm_stage.AVAILABLE_PRIORITIES[0][0])
|
||||
date_closed = fields.Datetime('Closed Date', readonly=True, copy=False)
|
||||
|
||||
stage_id = fields.Many2one('crm.stage', string='Stage', track_visibility='onchange', index=True,
|
||||
@@ -170,7 +171,7 @@ class Lead(FormatAddress, models.Model):
|
||||
stages = Stage.browse(stage_ids)
|
||||
result = stages.name_get()
|
||||
# restore order of the search
|
||||
order_mapping = dict((item[0], stage_ids.index(item[0])) for item in result) # match stage_id with its index
|
||||
order_mapping = {item[0]: stage_ids.index(item[0]) for item in result} # match stage_id with its index
|
||||
result.sort(key=lambda item: order_mapping.get(item[0]))
|
||||
|
||||
fold = {}
|
||||
@@ -191,7 +192,7 @@ class Lead(FormatAddress, models.Model):
|
||||
lead_date = fields.Date.from_string(lead.date_action)
|
||||
if lead_date >= today:
|
||||
kanban_state = 'green'
|
||||
elif lead_date < today:
|
||||
else:
|
||||
kanban_state = 'grey'
|
||||
lead.kanban_state = kanban_state
|
||||
|
||||
@@ -214,7 +215,7 @@ class Lead(FormatAddress, models.Model):
|
||||
@api.multi
|
||||
def _compute_meeting_count(self):
|
||||
meeting_data = self.env['calendar.event'].read_group([('opportunity_id', 'in', self.ids)], ['opportunity_id'], ['opportunity_id'])
|
||||
mapped_data = dict([(m['opportunity_id'][0], m['opportunity_id_count']) for m in meeting_data])
|
||||
mapped_data = {m['opportunity_id'][0]: m['opportunity_id_count'] for m in meeting_data}
|
||||
for lead in self:
|
||||
lead.meeting_count = mapped_data.get(lead.id, 0)
|
||||
|
||||
@@ -230,12 +231,11 @@ class Lead(FormatAddress, models.Model):
|
||||
|
||||
@api.onchange('stage_id')
|
||||
def _onchange_stage_id(self):
|
||||
values = self._onchange_stage_id_values(self.stage_id.id if self.stage_id else False)
|
||||
values = self._onchange_stage_id_values(self.stage_id.id)
|
||||
self.update(values)
|
||||
|
||||
def _onchange_partner_id_values(self, partner_id):
|
||||
""" returns the new values when partner_id has changed """
|
||||
values = {}
|
||||
if partner_id:
|
||||
partner = self.env['res.partner'].browse(partner_id)
|
||||
|
||||
@@ -243,7 +243,7 @@ class Lead(FormatAddress, models.Model):
|
||||
if not partner_name and partner.is_company:
|
||||
partner_name = partner.name
|
||||
|
||||
values = {
|
||||
return {
|
||||
'partner_name': partner_name,
|
||||
'contact_name': partner.name if not partner.is_company else False,
|
||||
'title': partner.title.id,
|
||||
@@ -259,7 +259,7 @@ class Lead(FormatAddress, models.Model):
|
||||
'zip': partner.zip,
|
||||
'function': partner.function,
|
||||
}
|
||||
return values
|
||||
return {}
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _onchange_partner_id(self):
|
||||
@@ -305,11 +305,14 @@ class Lead(FormatAddress, models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
# set up context used to find the lead's sales team which is needed
|
||||
# to correctly set the default stage_id
|
||||
context = dict(self._context or {})
|
||||
if vals.get('type') and not self._context.get('default_type'):
|
||||
context['default_type'] = vals.get('type')
|
||||
if vals.get('team_id') and not self._context.get('default_team_id'):
|
||||
context['default_team_id'] = vals.get('team_id')
|
||||
|
||||
if vals.get('user_id') and 'date_open' not in vals:
|
||||
vals['date_open'] = fields.Datetime.now()
|
||||
# context: no_log, because subtype already handle this
|
||||
@@ -376,8 +379,8 @@ class Lead(FormatAddress, models.Model):
|
||||
def action_set_won(self):
|
||||
""" Won semantic: probability = 100 (active untouched) """
|
||||
for lead in self:
|
||||
stage_id = lead.stage_find(domain=[('probability', '=', 100.0), ('on_change', '=', True)])
|
||||
lead.write({'stage_id': stage_id, 'probability': 100})
|
||||
stage_id = lead._stage_find(domain=[('probability', '=', 100.0), ('on_change', '=', True)])
|
||||
lead.write({'stage_id': stage_id.id, 'probability': 100})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -386,7 +389,7 @@ class Lead(FormatAddress, models.Model):
|
||||
:return dict: dictionary value for created Meeting view
|
||||
"""
|
||||
self.ensure_one()
|
||||
action = self.env['ir.actions.act_window'].for_xml_id('calendar', 'action_calendar_event')
|
||||
action = self.env.ref('calendar.action_calendar_event').read()[0]
|
||||
partner_ids = self.env.user.partner_id.ids
|
||||
if self.partner_id:
|
||||
partner_ids.append(self.partner_id.id)
|
||||
@@ -408,12 +411,11 @@ class Lead(FormatAddress, models.Model):
|
||||
# Business Methods
|
||||
# ----------------------------------------
|
||||
|
||||
@api.multi
|
||||
def stage_find(self, team_id=False, domain=None, order='sequence'):
|
||||
def _stage_find(self, team_id=False, domain=None, order='sequence'):
|
||||
""" Determine the stage of the current lead with its teams, the given domain and the given team_id
|
||||
:param team_id
|
||||
:param domain : base search domain for stage
|
||||
:returns crm.stage record identifier
|
||||
:returns crm.stage recordset
|
||||
"""
|
||||
# collect all team_ids by adding given one, and the ones related to the current leads
|
||||
team_ids = set()
|
||||
@@ -431,10 +433,7 @@ class Lead(FormatAddress, models.Model):
|
||||
if domain:
|
||||
search_domain += list(domain)
|
||||
# perform search, return the first found
|
||||
stage = self.env['crm.stage'].search(search_domain, order=order, limit=1)
|
||||
if stage:
|
||||
return stage.id
|
||||
return False
|
||||
return self.env['crm.stage'].search(search_domain, order=order, limit=1)
|
||||
|
||||
@api.multi
|
||||
def _merge_get_result_type(self):
|
||||
@@ -448,9 +447,8 @@ class Lead(FormatAddress, models.Model):
|
||||
:param list opps: list of browse records containing the leads/opps to process
|
||||
:return string type: the type of the final element
|
||||
"""
|
||||
for record in self:
|
||||
if record.type == 'opportunity':
|
||||
return 'opportunity'
|
||||
if any(record.type == 'opportunity' for record in self):
|
||||
return 'opportunity'
|
||||
return 'lead'
|
||||
|
||||
@api.multi
|
||||
@@ -616,20 +614,16 @@ class Lead(FormatAddress, models.Model):
|
||||
# Sorting the leads/opps according to the confidence level of its stage, which relates to the probability of winning it
|
||||
# The confidence level increases with the stage sequence, except when the stage probability is 0.0 (Lost cases)
|
||||
# An Opportunity always has higher confidence level than a lead, unless its stage probability is 0.0
|
||||
sequenced_opps = []
|
||||
for opportunity in self:
|
||||
def opps_key(opportunity):
|
||||
sequence = -1
|
||||
if opportunity.stage_id and opportunity.stage_id.on_change:
|
||||
if opportunity.stage_id.on_change:
|
||||
sequence = opportunity.stage_id.sequence
|
||||
sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity))
|
||||
sequenced_opps.sort(reverse=True)
|
||||
|
||||
sorted_opportunities = [item[1] for item in sequenced_opps] # get sorted list of crm.lead record
|
||||
return (sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id
|
||||
opportunities = self.sorted(key=opps_key, reverse=True)
|
||||
|
||||
# get SORTED recordset of head and tail, and complete list
|
||||
opportunities = self.browse([opportunity.id for opportunity in sorted_opportunities])
|
||||
opportunities_head = sorted_opportunities[0]
|
||||
opportunities_tail = self.browse([opportunity.id for opportunity in sorted_opportunities[1:]])
|
||||
opportunities_head = opportunities[0]
|
||||
opportunities_tail = opportunities[1:]
|
||||
|
||||
# merge all the sorted opportunity. This means the value of
|
||||
# the first (head opp) will be a priority.
|
||||
@@ -704,10 +698,10 @@ class Lead(FormatAddress, models.Model):
|
||||
'date_conversion': fields.Datetime.now(),
|
||||
}
|
||||
if not self.stage_id:
|
||||
stage_id = self.stage_find(team_id=team_id)
|
||||
value['stage_id'] = stage_id
|
||||
if stage_id:
|
||||
value['probability'] = self.env['crm.stage'].browse(stage_id).probability
|
||||
stage = self._stage_find(team_id=team_id)
|
||||
value['stage_id'] = stage.id
|
||||
if stage:
|
||||
value['probability'] = stage.probability
|
||||
return value
|
||||
|
||||
@api.multi
|
||||
@@ -800,9 +794,9 @@ class Lead(FormatAddress, models.Model):
|
||||
if action == 'create':
|
||||
partner = lead._create_lead_partner()
|
||||
partner_id = partner.id
|
||||
partner.write({'team_id': lead.team_id.id if lead.team_id else False})
|
||||
partner.team_id = lead.team_id
|
||||
if partner_id:
|
||||
lead.write({'partner_id': partner_id})
|
||||
lead.partner_id = partner_id
|
||||
partner_ids[lead.id] = partner_id
|
||||
return partner_ids
|
||||
|
||||
@@ -847,8 +841,8 @@ class Lead(FormatAddress, models.Model):
|
||||
'res_id': self.id,
|
||||
'view_id': False,
|
||||
'views': [
|
||||
(form_view.id if form_view else False, 'form'),
|
||||
(tree_view.id if tree_view else False, 'tree'),
|
||||
(form_view.id, 'form'),
|
||||
(tree_view.id, 'tree'),
|
||||
(False, 'kanban'),
|
||||
(False, 'calendar'),
|
||||
(False, 'graph')
|
||||
@@ -872,8 +866,8 @@ class Lead(FormatAddress, models.Model):
|
||||
'res_id': self.id,
|
||||
'view_id': False,
|
||||
'views': [
|
||||
(form_view.id if form_view else False, 'form'),
|
||||
(tree_view.id if form_view else False, 'tree'),
|
||||
(form_view.id, 'form'),
|
||||
(tree_view.id, 'tree'),
|
||||
(False, 'calendar'),
|
||||
(False, 'graph')
|
||||
],
|
||||
@@ -882,11 +876,6 @@ class Lead(FormatAddress, models.Model):
|
||||
|
||||
@api.model
|
||||
def get_empty_list_help(self, help):
|
||||
additionnal_context = {
|
||||
'empty_list_help_model': 'crm.team',
|
||||
'empty_list_help_id': self._context.get('default_team_id', False),
|
||||
'empty_list_help_document_name': _("opportunities")
|
||||
}
|
||||
if help:
|
||||
alias_record = self.env.ref("crm.mail_alias_lead_info", raise_if_not_found=False)
|
||||
if alias_record and alias_record.alias_domain and alias_record.alias_name:
|
||||
@@ -896,7 +885,11 @@ class Lead(FormatAddress, models.Model):
|
||||
create new opportunity. Update your business card, phone book, social media,...
|
||||
Send an email right now and see it here.""") % (email_link,)
|
||||
return '<p class="oe_view_nocontent_create">%s</p>%s<p>%s</p>' % (_('Click to add a new opportunity'), help, dynamic_help)
|
||||
return super(Lead, self.with_context(**additionnal_context)).get_empty_list_help(help)
|
||||
return super(Lead, self.with_context(
|
||||
empty_list_help_model='crm.team',
|
||||
empty_list_help_id=self._context.get('default_team_id', False),
|
||||
empty_list_help_document_name=_("opportunities"),
|
||||
)).get_empty_list_help(help)
|
||||
|
||||
@api.multi
|
||||
def log_meeting(self, meeting_subject, meeting_date, duration):
|
||||
@@ -943,36 +936,36 @@ class Lead(FormatAddress, models.Model):
|
||||
'nb_opportunities': 0,
|
||||
}
|
||||
|
||||
opportunities = self.search_read([('type', '=', 'opportunity'), ('user_id', '=', self._uid)], ['date_deadline', 'next_activity_id', 'date_action', 'date_closed', 'planned_revenue'])
|
||||
opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid)])
|
||||
|
||||
for opp in opportunities:
|
||||
# Expected closing
|
||||
if opp['date_deadline']:
|
||||
date_deadline = fields.Date.from_string(opp['date_deadline'])
|
||||
if opp.date_deadline:
|
||||
date_deadline = fields.Date.from_string(opp.date_deadline)
|
||||
if date_deadline == date.today():
|
||||
result['closing']['today'] += 1
|
||||
if date_deadline >= date.today() and date_deadline <= date.today() + timedelta(days=7):
|
||||
if date.today() <= date_deadline <= date.today() + timedelta(days=7):
|
||||
result['closing']['next_7_days'] += 1
|
||||
if date_deadline < date.today():
|
||||
result['closing']['overdue'] += 1
|
||||
# Next activities
|
||||
if opp['next_activity_id'] and opp['date_action']:
|
||||
date_action = fields.Date.from_string(opp['date_action'])
|
||||
if opp.next_activity_id and opp.date_action:
|
||||
date_action = fields.Date.from_string(opp.date_action)
|
||||
if date_action == date.today():
|
||||
result['activity']['today'] += 1
|
||||
if date_action >= date.today() and date_action <= date.today() + timedelta(days=7):
|
||||
if date.today() <= date_action <= date.today() + timedelta(days=7):
|
||||
result['activity']['next_7_days'] += 1
|
||||
if date_action < date.today():
|
||||
result['activity']['overdue'] += 1
|
||||
# Won in Opportunities
|
||||
if opp['date_closed']:
|
||||
date_closed = fields.Date.from_string(opp['date_closed'])
|
||||
if date_closed <= date.today() and date_closed >= date.today().replace(day=1):
|
||||
if opp['planned_revenue']:
|
||||
result['won']['this_month'] += opp['planned_revenue']
|
||||
elif date_closed < date.today().replace(day=1) and date_closed >= date.today().replace(day=1) - relativedelta(months=+1):
|
||||
if opp['planned_revenue']:
|
||||
result['won']['last_month'] += opp['planned_revenue']
|
||||
if opp.date_closed:
|
||||
date_closed = fields.Date.from_string(opp.date_closed)
|
||||
if date.today().replace(day=1) <= date_closed <= date.today():
|
||||
if opp.planned_revenue:
|
||||
result['won']['this_month'] += opp.planned_revenue
|
||||
elif date.today() + relativedelta(months=-1, day=1) <= date_closed < date.today().replace(day=1):
|
||||
if opp.planned_revenue:
|
||||
result['won']['last_month'] += opp.planned_revenue
|
||||
|
||||
result['nb_opportunities'] = len(opportunities)
|
||||
|
||||
@@ -995,9 +988,9 @@ class Lead(FormatAddress, models.Model):
|
||||
for activity in activites_done:
|
||||
if activity['date']:
|
||||
date_act = fields.Date.from_string(activity['date'])
|
||||
if date_act <= date.today() and date_act >= date.today().replace(day=1):
|
||||
if date.today().replace(day=1) <= date_act <= date.today():
|
||||
result['done']['this_month'] += 1
|
||||
elif date_act < date.today().replace(day=1) and date_act >= date.today().replace(day=1) - relativedelta(months=+1):
|
||||
elif date.today() + relativedelta(months=-1, day=1) <= date_act < date.today().replace(day=1):
|
||||
result['done']['last_month'] += 1
|
||||
|
||||
# Meetings
|
||||
@@ -1014,7 +1007,7 @@ class Lead(FormatAddress, models.Model):
|
||||
start = datetime.strptime(meeting['start'], tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
if start == date.today():
|
||||
result['meeting']['today'] += 1
|
||||
if start >= date.today() and start <= date.today() + timedelta(days=7):
|
||||
if date.today() <= start <= date.today() + timedelta(days=7):
|
||||
result['meeting']['next_7_days'] += 1
|
||||
|
||||
result['done']['target'] = self.env.user.target_sales_done
|
||||
@@ -1093,11 +1086,10 @@ class Lead(FormatAddress, models.Model):
|
||||
def message_get_reply_to(self, res_ids, default=None):
|
||||
leads = self.sudo().browse(res_ids)
|
||||
aliases = self.env['crm.team'].message_get_reply_to(leads.mapped('team_id').ids, default=default)
|
||||
return dict((lead.id, aliases.get(lead.team_id and lead.team_id.id or 0, False)) for lead in leads)
|
||||
return {lead.id: aliases.get(lead.team_id.id or 0, False) for lead in leads}
|
||||
|
||||
@api.multi
|
||||
def get_formview_id(self):
|
||||
self.ensure_one()
|
||||
if self.type == 'opportunity':
|
||||
view_id = self.env.ref('crm.crm_case_form_view_oppor').id
|
||||
else:
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
from odoo.tools.translate import _
|
||||
|
||||
|
||||
class Team(models.Model):
|
||||
@@ -21,7 +20,7 @@ class Team(models.Model):
|
||||
return 'crm.lead'
|
||||
|
||||
def get_alias_values(self):
|
||||
has_group_use_lead = self.env['res.users'].has_group('crm.group_use_lead')
|
||||
has_group_use_lead = self.env.user.has_group('crm.group_use_lead')
|
||||
values = super(Team, self).get_alias_values()
|
||||
values['alias_defaults'] = defaults = eval(self.alias_defaults or "{}")
|
||||
defaults['type'] = 'lead' if has_group_use_lead and self.use_leads else 'opportunity'
|
||||
@@ -64,22 +63,18 @@ class Team(models.Model):
|
||||
|
||||
action_context = eval(action['context'], {'uid': self.env.uid})
|
||||
if user_team_id:
|
||||
action_context.update({
|
||||
'default_team_id': user_team_id,
|
||||
})
|
||||
action_context['default_team_id'] = user_team_id
|
||||
|
||||
tree_view_id = self.env.ref('crm.crm_case_tree_view_oppor').id
|
||||
form_view_id = self.env.ref('crm.crm_case_form_view_oppor').id
|
||||
kanb_view_id = self.env.ref('crm.crm_case_kanban_view_leads').id
|
||||
action.update({
|
||||
'views': [
|
||||
action['views'] = [
|
||||
[kanb_view_id, 'kanban'],
|
||||
[tree_view_id, 'tree'],
|
||||
[form_view_id, 'form'],
|
||||
[False, 'graph'],
|
||||
[False, 'calendar'],
|
||||
[False, 'pivot']
|
||||
],
|
||||
'context': action_context,
|
||||
})
|
||||
]
|
||||
action['context'] = action_context
|
||||
return action
|
||||
|
||||
@@ -54,8 +54,9 @@ class CRMSettings(models.TransientModel):
|
||||
def set_default_alias_prefix(self):
|
||||
for record in self:
|
||||
alias = self._find_default_lead_alias_id()
|
||||
if not alias:
|
||||
alias = self.env['mail.alias'].with_context(alias_model_name='crm.lead', alias_parent_model_name='crm.team').create({'alias_name': record.alias_prefix})
|
||||
else:
|
||||
if alias:
|
||||
alias.write({'alias_name': record.alias_prefix})
|
||||
else:
|
||||
self.env['mail.alias'].with_context(alias_model_name='crm.lead', alias_parent_model_name='crm.team').create({'alias_name': record.alias_prefix})
|
||||
|
||||
return True
|
||||
|
||||
@@ -37,7 +37,7 @@ class Partner(models.Model):
|
||||
def schedule_meeting(self):
|
||||
partner_ids = self.ids
|
||||
partner_ids.append(self.env.user.partner_id.id)
|
||||
action = self.env['ir.actions.act_window'].for_xml_id('calendar', 'action_calendar_event')
|
||||
action = self.env.ref('calendar.action_calendar_event').read()[0]
|
||||
action['context'] = {
|
||||
'search_default_partner_ids': self._context['partner_name'],
|
||||
'default_partner_ids': partner_ids,
|
||||
|
||||
@@ -31,7 +31,6 @@ class ActivityReport(models.Model):
|
||||
probability = fields.Float('Probability', group_operator='avg', readonly=True)
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, 'crm_activity_report')
|
||||
self._cr.execute("""
|
||||
CREATE OR REPLACE VIEW crm_activity_report AS (
|
||||
select
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.crm.models import crm_stage
|
||||
from odoo import fields, models, tools
|
||||
|
||||
from ..models import crm_stage
|
||||
|
||||
class OpportunityReport(models.Model):
|
||||
""" CRM Opportunity Analysis """
|
||||
@@ -30,7 +30,6 @@ class OpportunityReport(models.Model):
|
||||
nbr_activities = fields.Integer('# of Activities', readonly=True)
|
||||
city = fields.Char('City')
|
||||
country_id = fields.Many2one('res.country', string='Country', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', readonly=True)
|
||||
probability = fields.Float(string='Probability', digits=(16, 2), readonly=True, group_operator="avg")
|
||||
total_revenue = fields.Float(string='Total Revenue', digits=(16, 2), readonly=True)
|
||||
expected_revenue = fields.Float(string='Probable Turnover', digits=(16, 2), readonly=True)
|
||||
@@ -50,7 +49,6 @@ class OpportunityReport(models.Model):
|
||||
medium_id = fields.Many2one('utm.medium', string='Medium', readonly=True)
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, 'crm_opportunity_report')
|
||||
self._cr.execute("""
|
||||
CREATE OR REPLACE VIEW crm_opportunity_report AS (
|
||||
SELECT
|
||||
|
||||
@@ -40,7 +40,7 @@ class TestCRMLead(TestCrmCases):
|
||||
# Now I change the stage of opportunity to won.
|
||||
lead.action_set_won()
|
||||
# I check stage of opp should won, after change stage.
|
||||
stage_id = lead.stage_find(domain=[('probability', '=', 100.0)])
|
||||
stage_id = lead._stage_find(domain=[('probability', '=', 100.0)])
|
||||
self.assertEqual(stage_id, lead.stage_id, "Stage of opportunity is incorrect!")
|
||||
|
||||
def test_crm_lead_message(self):
|
||||
|
||||
@@ -285,15 +285,13 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
except ValidationError:
|
||||
_logger.info('Skip recursive partner hierarchies for parent_id %s of partner: %s', parent_id, dst_partner.id)
|
||||
|
||||
@api.model
|
||||
@mute_logger('openerp.osv.expression', 'openerp.models')
|
||||
def _merge(self, partner_ids, dst_partner=None):
|
||||
""" private implementation of merge partner
|
||||
:param partner_ids : ids of partner to merge
|
||||
:param dst_partner : record of destination res.partner
|
||||
"""
|
||||
Partner = self.env['res.partner']
|
||||
partner_ids = Partner.browse(partner_ids).exists().ids
|
||||
partner_ids = Partner.browse(partner_ids).exists()
|
||||
if len(partner_ids) < 2:
|
||||
return
|
||||
|
||||
@@ -301,10 +299,10 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
raise UserError(_("For safety reasons, you cannot merge more than 3 contacts together. You can re-open the wizard several times if needed."))
|
||||
|
||||
# check if the list of partners to merge contains child/parent relation
|
||||
child_ids = set()
|
||||
child_ids = self.env['res.partner']
|
||||
for partner_id in partner_ids:
|
||||
child_ids = child_ids.union(set(Partner.search([('id', 'child_of', [partner_id])]).ids) - set([partner_id]))
|
||||
if set(partner_ids).intersection(child_ids):
|
||||
child_ids |= Partner.search([('id', 'child_of', [partner_id.id])]) - partner_id
|
||||
if partner_ids & child_ids:
|
||||
raise UserError(_("You cannot merge a contact with one of his parent."))
|
||||
|
||||
# check only admin can merge partners with different emails
|
||||
@@ -312,23 +310,22 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
raise UserError(_("All contacts must have the same email. Only the Administrator can merge contacts with different emails."))
|
||||
|
||||
# remove dst_partner from partners to merge
|
||||
if dst_partner and dst_partner.id in partner_ids:
|
||||
src_partners = Partner.browse([pid for pid in partner_ids if pid != dst_partner.id])
|
||||
if dst_partner and dst_partner in partner_ids:
|
||||
src_partners = partner_ids - dst_partner
|
||||
else:
|
||||
ordered_partners = self._get_ordered_partner(partner_ids)
|
||||
ordered_partners = self._get_ordered_partner(partner_ids.ids)
|
||||
dst_partner = ordered_partners[-1]
|
||||
src_partners = ordered_partners[:-1]
|
||||
_logger.info("dst_partner: %s", dst_partner.id)
|
||||
|
||||
# FIXME : is it still required to make and exception for account.move.line since accounting v9.0 ?
|
||||
# FIXME: is it still required to make and exception for account.move.line since accounting v9.0 ?
|
||||
if SUPERUSER_ID != self.env.uid and 'account.move.line' in self.env and self.env['account.move.line'].sudo().search([('partner_id', 'in', [partner.id for partner in src_partners])]):
|
||||
raise UserError(_("Only the destination contact may be linked to existing Journal Items. Please ask the Administrator if you need to merge several contacts linked to existing Journal Items."))
|
||||
|
||||
# call sub methods to do the merge
|
||||
call_it = lambda function: function(src_partners, dst_partner)
|
||||
call_it(self._update_foreign_keys)
|
||||
call_it(self._update_reference_fields)
|
||||
call_it(self._update_values)
|
||||
self._update_foreign_keys(src_partners, dst_partner)
|
||||
self._update_reference_fields(src_partners, dst_partner)
|
||||
self._update_values(src_partners, dst_partner)
|
||||
|
||||
_logger.info('(uid = %s) merged the partners %r with %s', self._uid, src_partners.ids, dst_partner.id)
|
||||
dst_partner.message_post(body='%s %s' % (_("Merged with the following partners:"), ", ".join('%s <%s> (ID %s)' % (p.name, p.email or 'n/a', p.id) for p in src_partners)))
|
||||
@@ -380,9 +377,7 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
])
|
||||
|
||||
if maximum_group:
|
||||
text.extend([
|
||||
"LIMIT %s" % maximum_group,
|
||||
])
|
||||
text.append("LIMIT %s" % maximum_group,)
|
||||
|
||||
return ' '.join(text)
|
||||
|
||||
@@ -410,25 +405,24 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
:param aggr_ids : stringified list of partner ids separated with a comma (sql array_agg)
|
||||
:param models : dict mapping a model name with its foreign key with res_partner table
|
||||
"""
|
||||
for model, field in models.iteritems():
|
||||
proxy = self.env[model]
|
||||
if proxy.search_count([(field, 'in', aggr_ids)]):
|
||||
return True
|
||||
return False
|
||||
return any(
|
||||
self.env[model].search_count([(field, 'in', aggr_ids)], limit=1)
|
||||
for model, field in models.iteritems()
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_ordered_partner(self, partner_ids):
|
||||
""" Helper : returns a `res.partner` recordset ordered by create_date/active fields
|
||||
:param partner_ids : list of partner ids to sort
|
||||
"""
|
||||
partners = self.env['res.partner'].browse(partner_ids)
|
||||
ordered_partners = sorted(sorted(partners, key=operator.attrgetter('create_date'), reverse=True), key=operator.attrgetter('active'), reverse=True)
|
||||
return self.env['res.partner'].browse([partner.id for partner in ordered_partners])
|
||||
return self.env['res.partner'].browse(partner_ids).sorted(
|
||||
key=lambda p: (p.active, p.create_date),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _compute_models(self):
|
||||
""" Compute the different models needed by the system if you want to exclude some partners. """
|
||||
self.ensure_one()
|
||||
model_mapping = {}
|
||||
if self.exclude_contact:
|
||||
model_mapping['res.users'] = 'partner_id'
|
||||
@@ -448,7 +442,6 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
@api.multi
|
||||
def action_skip(self):
|
||||
""" Skip this wizard line. Don't compute any thing, and simply redirect to the new step."""
|
||||
self.ensure_one()
|
||||
if self.current_line_id:
|
||||
self.current_line_id.unlink()
|
||||
return self._action_next_screen()
|
||||
@@ -459,8 +452,7 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
next wizard line. Each line is a subset of partner that can be merged together.
|
||||
If no line left, the end screen will be displayed (but an action is still returned).
|
||||
"""
|
||||
self.ensure_one()
|
||||
self.refresh() # TODO JEM : it is deprecated, so still usefull ?
|
||||
self.invalidate_cache() # FIXME: is this still necessary?
|
||||
values = {}
|
||||
if self.line_ids:
|
||||
# in this case, we try to find the next record.
|
||||
@@ -541,7 +533,7 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
"""
|
||||
self.ensure_one()
|
||||
self.action_start_manual_process() # here we don't redirect to the next screen, since it is automatic process
|
||||
self.refresh() # TODO JEM : deprecated, what to do ?
|
||||
self.invalidate_cache() # FIXME: is this still necessary?
|
||||
|
||||
for line in self.line_ids:
|
||||
partner_ids = literal_eval(line.aggr_ids)
|
||||
@@ -643,8 +635,6 @@ class MergePartnerAutomatic(models.TransientModel):
|
||||
""" Merge Contact button. Merge the selected partners, and redirect to
|
||||
the end screen (since there is no other wizard line to process.
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
if not self.partner_ids:
|
||||
self.write({'state': 'finished'})
|
||||
return {
|
||||
|
||||
@@ -11,8 +11,5 @@ class CrmLeadLost(models.TransientModel):
|
||||
|
||||
@api.multi
|
||||
def action_lost_reason_apply(self):
|
||||
res = False
|
||||
for wizard in self:
|
||||
self.lead_id.lost_reason = self.lost_reason_id
|
||||
res = self.lead_id.action_set_lost()
|
||||
return res
|
||||
self.lead_id.lost_reason = self.lost_reason_id
|
||||
return self.lead_id.action_set_lost()
|
||||
|
||||
@@ -20,29 +20,28 @@ class Lead2OpportunityPartner(models.TransientModel):
|
||||
"""
|
||||
result = super(Lead2OpportunityPartner, self).default_get(fields)
|
||||
if self._context.get('active_id'):
|
||||
tomerge = [int(self._context['active_id'])]
|
||||
tomerge = {int(self._context['active_id'])}
|
||||
|
||||
partner_id = result.get('partner_id')
|
||||
lead = self.env['crm.lead'].browse(self._context['active_id'])
|
||||
email = lead.partner_id.email if lead.partner_id else lead.email_from
|
||||
|
||||
tomerge.extend(self._get_duplicated_leads(partner_id, email, include_lost=True).ids)
|
||||
tomerge = list(set(tomerge))
|
||||
tomerge.update(self._get_duplicated_leads(partner_id, email, include_lost=True).ids)
|
||||
|
||||
if 'action' in fields and not result.get('action'):
|
||||
result.update({'action': 'exist' if partner_id else 'create'})
|
||||
result['action'] = 'exist' if partner_id else 'create'
|
||||
if 'partner_id' in fields:
|
||||
result.update({'partner_id': partner_id})
|
||||
result['partner_id'] = partner_id
|
||||
if 'name' in fields:
|
||||
result.update({'name': 'merge' if len(tomerge) >= 2 else 'convert'})
|
||||
result['name'] = 'merge' if len(tomerge) >= 2 else 'convert'
|
||||
if 'opportunity_ids' in fields and len(tomerge) >= 2:
|
||||
result.update({'opportunity_ids': tomerge})
|
||||
result['opportunity_ids'] = list(tomerge)
|
||||
if lead.user_id:
|
||||
result.update({'user_id': lead.user_id.id})
|
||||
result['user_id'] = lead.user_id.id
|
||||
if lead.team_id:
|
||||
result.update({'team_id': lead.team_id.id})
|
||||
result['team_id'] = lead.team_id.id
|
||||
if not partner_id and not lead.contact_name:
|
||||
result.update({'action': 'nothing'})
|
||||
result['action'] = 'nothing'
|
||||
return result
|
||||
|
||||
name = fields.Selection([
|
||||
@@ -93,22 +92,20 @@ class Lead2OpportunityPartner(models.TransientModel):
|
||||
self.ensure_one()
|
||||
|
||||
res = False
|
||||
lead_ids = vals.get('lead_ids', [])
|
||||
team_id = vals.get('team_id', False)
|
||||
partner_id = vals.get('partner_id')
|
||||
|
||||
for lead in self.env['crm.lead'].browse(lead_ids):
|
||||
partner_id = self._create_partner(lead.id, self.action, partner_id or lead.partner_id.id)
|
||||
leads = self.env['crm.lead'].browse(vals.get('lead_ids'))
|
||||
for lead in leads:
|
||||
partner_id = self._create_partner(lead.id, self.action, vals.get('partner_id') or lead.partner_id.id)
|
||||
res = lead.convert_opportunity(partner_id, [], False)
|
||||
user_ids = vals.get('user_ids', False)
|
||||
user_ids = vals.get('user_ids')
|
||||
|
||||
leads_to_allocate = leads
|
||||
if self._context.get('no_force_assignation'):
|
||||
leads_to_allocate = self.env['crm.lead'].browse(lead_ids).filtered(lambda lead: not lead.user_id).ids
|
||||
else:
|
||||
leads_to_allocate = lead_ids
|
||||
leads_to_allocate = leads_to_allocate.filtered(lambda lead: not lead.user_id)
|
||||
|
||||
if user_ids:
|
||||
self.env['crm.lead'].browse(leads_to_allocate).allocate_salesman(user_ids, team_id=team_id)
|
||||
leads_to_allocate.allocate_salesman(user_ids, team_id=(vals.get('team_id')))
|
||||
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@@ -117,8 +114,6 @@ class Lead2OpportunityPartner(models.TransientModel):
|
||||
the freshly created opportunity view.
|
||||
"""
|
||||
self.ensure_one()
|
||||
Lead = self.env['crm.lead']
|
||||
opp_ids = self.opportunity_ids.ids
|
||||
values = {
|
||||
'team_id': self.team_id.id,
|
||||
}
|
||||
@@ -127,26 +122,23 @@ class Lead2OpportunityPartner(models.TransientModel):
|
||||
values['partner_id'] = self.partner_id.id
|
||||
|
||||
if self.name == 'merge':
|
||||
lead = self.env['crm.lead'].browse(opp_ids).merge_opportunity()
|
||||
lead_ids = [lead.id]
|
||||
lead_data = lead.read(['type', 'user_id'])[0]
|
||||
if lead_data['type'] == "lead":
|
||||
values.update({'lead_ids': lead_ids, 'user_ids': [self.user_id.id]})
|
||||
self.with_context(active_ids=lead_ids)._convert_opportunity(values)
|
||||
elif not self._context.get('no_force_assignation') or not lead_data['user_id']:
|
||||
values.update({'user_id': self.user_id.id})
|
||||
lead.write(values)
|
||||
leads = self.opportunity_ids.merge_opportunity()
|
||||
if leads.type == "lead":
|
||||
values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]})
|
||||
self.with_context(active_ids=leads.ids)._convert_opportunity(values)
|
||||
elif not self._context.get('no_force_assignation') or not leads.user_id:
|
||||
values['user_id'] = self.user_id.id
|
||||
leads.write(values)
|
||||
else:
|
||||
lead_ids = self._context.get('active_ids', [])
|
||||
values.update({'lead_ids': lead_ids, 'user_ids': [self.user_id.id]})
|
||||
leads = self.env['crm.lead'].browse(self._context.get('active_ids', []))
|
||||
values.update({'lead_ids': leads.ids, 'user_ids': [self.user_id.id]})
|
||||
self._convert_opportunity(values)
|
||||
for lead in self.env['crm.lead'].browse(lead_ids):
|
||||
for lead in leads:
|
||||
if lead.partner_id and lead.partner_id.user_id != lead.user_id:
|
||||
self.env['res.partner'].browse(lead.partner_id.id).write({'user_id': lead.user_id.id})
|
||||
|
||||
return self.env['crm.lead'].browse(list(lead_ids)[0]).redirect_opportunity_view()
|
||||
return leads[0].redirect_opportunity_view()
|
||||
|
||||
@api.model
|
||||
def _create_partner(self, lead_id, action, partner_id):
|
||||
""" Create partner based on action.
|
||||
:return dict: dictionary organized as followed: {lead_id: partner_assigned_id}
|
||||
@@ -229,10 +221,9 @@ class Lead2OpportunityMassConvert(models.TransientModel):
|
||||
@api.multi
|
||||
def mass_convert(self):
|
||||
self.ensure_one()
|
||||
active_ids = []
|
||||
if self.name == 'convert' and self.deduplicate:
|
||||
merged_lead_ids = []
|
||||
remaining_lead_ids = []
|
||||
merged_lead_ids = set()
|
||||
remaining_lead_ids = set()
|
||||
lead_selected = self._context.get('active_ids', [])
|
||||
for lead_id in lead_selected:
|
||||
if lead_id not in merged_lead_ids:
|
||||
@@ -240,11 +231,11 @@ class Lead2OpportunityMassConvert(models.TransientModel):
|
||||
duplicated_leads = self._get_duplicated_leads(lead.partner_id.id, lead.partner_id.email if lead.partner_id else lead.email_from)
|
||||
if len(duplicated_leads) > 1:
|
||||
lead = duplicated_leads.merge_opportunity()
|
||||
merged_lead_ids.extend(duplicated_leads.ids)
|
||||
remaining_lead_ids.append(lead.id)
|
||||
active_ids = set(self._context.get('active_ids', []))
|
||||
active_ids = active_ids.difference(merged_lead_ids)
|
||||
active_ids = active_ids.union(remaining_lead_ids)
|
||||
merged_lead_ids.update(duplicated_leads.ids)
|
||||
remaining_lead_ids.add(lead.id)
|
||||
active_ids = set(self._context.get('active_ids', {}))
|
||||
active_ids = (active_ids - merged_lead_ids) | remaining_lead_ids
|
||||
|
||||
self = self.with_context(active_ids=list(active_ids)) # only update active_ids when there are set
|
||||
no_force_assignation = self._context.get('no_force_assignation', not self.force_assignation)
|
||||
return self.with_context(no_force_assignation=no_force_assignation).action_apply()
|
||||
|
||||
@@ -22,13 +22,13 @@ class MergeOpportunity(models.TransientModel):
|
||||
""" Use active_ids from the context to fetch the leads/opps to merge.
|
||||
In order to get merged, these leads/opps can't be in 'Dead' or 'Closed'
|
||||
"""
|
||||
record_ids = self._context.get('active_ids', False)
|
||||
record_ids = self._context.get('active_ids')
|
||||
result = super(MergeOpportunity, self).default_get(fields)
|
||||
|
||||
if record_ids:
|
||||
if 'opportunity_ids' in fields:
|
||||
opp_ids = self.env['crm.lead'].browse(record_ids).filtered(lambda opp: opp.probability < 100).ids
|
||||
result.update({'opportunity_ids': opp_ids})
|
||||
result['opportunity_ids'] = opp_ids
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.translate import _
|
||||
|
||||
|
||||
class PartnerBinding(models.TransientModel):
|
||||
@@ -24,7 +23,7 @@ class PartnerBinding(models.TransientModel):
|
||||
partner_id = self._find_matching_partner()
|
||||
|
||||
if 'action' in fields and not res.get('action'):
|
||||
res['action'] = partner_id and 'exist' or 'create'
|
||||
res['action'] = 'exist' if partner_id else 'create'
|
||||
if 'partner_id' in fields:
|
||||
res['partner_id'] = partner_id
|
||||
return res
|
||||
@@ -43,23 +42,26 @@ class PartnerBinding(models.TransientModel):
|
||||
:return int partner_id if any, False otherwise
|
||||
"""
|
||||
# active model has to be a lead
|
||||
lead = None
|
||||
if self._context.get('active_model') == 'crm.lead' and self._context.get('active_id'):
|
||||
lead = self.env['crm.lead'].browse(self._context.get('active_id'))
|
||||
if self._context.get('active_model') != 'crm.lead' or not self._context.get('active_id'):
|
||||
return False
|
||||
|
||||
lead = self.env['crm.lead'].browse(self._context.get('active_id'))
|
||||
|
||||
# find the best matching partner for the active model
|
||||
partner_id = False
|
||||
if lead:
|
||||
Partner = self.env['res.partner']
|
||||
if lead.partner_id: # a partner is set already
|
||||
partner_id = lead.partner_id.id
|
||||
elif lead.email_from: # search through the existing partners based on the lead's email
|
||||
partner = Partner.search([('email', '=', lead.email_from)], limit=1)
|
||||
partner_id = partner.id if partner else False
|
||||
elif lead.partner_name: # search through the existing partners based on the lead's partner or contact name
|
||||
partner = Partner.search([('name', 'ilike', '%' + lead.partner_name + '%')], limit=1)
|
||||
partner_id = partner.id if partner else False
|
||||
elif lead.contact_name:
|
||||
partner = Partner.search([('name', 'ilike', '%' + lead.contact_name+'%')], limit=1)
|
||||
partner_id = partner.id if partner else False
|
||||
return partner_id
|
||||
Partner = self.env['res.partner']
|
||||
if lead.partner_id: # a partner is set already
|
||||
return lead.partner_id.id
|
||||
|
||||
if lead.email_from: # search through the existing partners based on the lead's email
|
||||
partner = Partner.search([('email', '=', lead.email_from)], limit=1)
|
||||
return partner.id
|
||||
|
||||
if lead.partner_name: # search through the existing partners based on the lead's partner or contact name
|
||||
partner = Partner.search([('name', 'ilike', '%' + lead.partner_name + '%')], limit=1)
|
||||
return partner.id
|
||||
|
||||
if lead.contact_name:
|
||||
partner = Partner.search([('name', 'ilike', '%' + lead.contact_name+'%')], limit=1)
|
||||
return partner.id
|
||||
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user