diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 068d4c75d0c..3502edfc262 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -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 '
%s
%s%s
' % (_('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: diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index a59f0b3f1ac..39ac597ce41 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -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 diff --git a/addons/crm/models/res_config.py b/addons/crm/models/res_config.py index e9a311a26ef..de19101eb4a 100644 --- a/addons/crm/models/res_config.py +++ b/addons/crm/models/res_config.py @@ -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 diff --git a/addons/crm/models/res_partner.py b/addons/crm/models/res_partner.py index eef272c1463..a973700c15f 100644 --- a/addons/crm/models/res_partner.py +++ b/addons/crm/models/res_partner.py @@ -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, diff --git a/addons/crm/report/crm_activity_report.py b/addons/crm/report/crm_activity_report.py index 489838e0686..3a7f8eb9ef4 100644 --- a/addons/crm/report/crm_activity_report.py +++ b/addons/crm/report/crm_activity_report.py @@ -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 diff --git a/addons/crm/report/crm_opportunity_report.py b/addons/crm/report/crm_opportunity_report.py index f8627bfeb76..a44bb233592 100644 --- a/addons/crm/report/crm_opportunity_report.py +++ b/addons/crm/report/crm_opportunity_report.py @@ -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 diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py index 156c7ff48db..7ff20c4ab5e 100644 --- a/addons/crm/tests/test_crm_lead.py +++ b/addons/crm/tests/test_crm_lead.py @@ -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): diff --git a/addons/crm/wizard/base_partner_merge.py b/addons/crm/wizard/base_partner_merge.py index 3a8c406599f..21b3e5335b4 100644 --- a/addons/crm/wizard/base_partner_merge.py +++ b/addons/crm/wizard/base_partner_merge.py @@ -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 { diff --git a/addons/crm/wizard/crm_lead_lost.py b/addons/crm/wizard/crm_lead_lost.py index 6e67e6fa77f..bc79cccae22 100644 --- a/addons/crm/wizard/crm_lead_lost.py +++ b/addons/crm/wizard/crm_lead_lost.py @@ -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() diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 3a3aee807d9..4bca3debb07 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -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() diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py index 102b1029660..c0e2a6cc7ba 100644 --- a/addons/crm/wizard/crm_merge_opportunities.py +++ b/addons/crm/wizard/crm_merge_opportunities.py @@ -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 diff --git a/addons/crm/wizard/crm_partner_binding.py b/addons/crm/wizard/crm_partner_binding.py index 0270a656200..aee80574444 100644 --- a/addons/crm/wizard/crm_partner_binding.py +++ b/addons/crm/wizard/crm_partner_binding.py @@ -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