summaryrefslogtreecommitdiff
path: root/addons/crm/wizard
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/crm/wizard
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/crm/wizard')
-rw-r--r--addons/crm/wizard/__init__.py7
-rw-r--r--addons/crm/wizard/crm_lead_lost.py14
-rw-r--r--addons/crm/wizard/crm_lead_lost_views.xml27
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity.py170
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_mass.py110
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml63
-rw-r--r--addons/crm/wizard/crm_lead_to_opportunity_views.xml53
-rw-r--r--addons/crm/wizard/crm_merge_opportunities.py56
-rw-r--r--addons/crm/wizard/crm_merge_opportunities_views.xml45
9 files changed, 545 insertions, 0 deletions
diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py
new file mode 100644
index 00000000..e424c548
--- /dev/null
+++ b/addons/crm/wizard/__init__.py
@@ -0,0 +1,7 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import crm_lead_lost
+from . import crm_lead_to_opportunity
+from . import crm_lead_to_opportunity_mass
+from . import crm_merge_opportunities
diff --git a/addons/crm/wizard/crm_lead_lost.py b/addons/crm/wizard/crm_lead_lost.py
new file mode 100644
index 00000000..8f64a405
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_lost.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models
+
+
+class CrmLeadLost(models.TransientModel):
+ _name = 'crm.lead.lost'
+ _description = 'Get Lost Reason'
+
+ lost_reason_id = fields.Many2one('crm.lost.reason', 'Lost Reason')
+
+ def action_lost_reason_apply(self):
+ leads = self.env['crm.lead'].browse(self.env.context.get('active_ids'))
+ return leads.action_set_lost(lost_reason=self.lost_reason_id.id)
diff --git a/addons/crm/wizard/crm_lead_lost_views.xml b/addons/crm/wizard/crm_lead_lost_views.xml
new file mode 100644
index 00000000..2128db8a
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_lost_views.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<odoo>
+ <record id="crm_lead_lost_view_form" model="ir.ui.view">
+ <field name="name">crm.lead.lost.form</field>
+ <field name="model">crm.lead.lost</field>
+ <field name="arch" type="xml">
+ <form string="Lost Reason">
+ <group class="oe_title">
+ <field name="lost_reason_id" options="{'no_create_edit': True}" />
+ </group>
+ <footer>
+ <button name="action_lost_reason_apply" string="Submit" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="crm_lead_lost_action" model="ir.actions.act_window">
+ <field name="name">Lost Reason</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">crm.lead.lost</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="crm_lead_lost_view_form"/>
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py
new file mode 100644
index 00000000..a3db5d2a
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_to_opportunity.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+from odoo.tools.translate import _
+
+
+class Lead2OpportunityPartner(models.TransientModel):
+ _name = 'crm.lead2opportunity.partner'
+ _description = 'Convert Lead to Opportunity (not in mass)'
+
+ @api.model
+ def default_get(self, fields):
+
+ """ Allow support of active_id / active_model instead of jut default_lead_id
+ to ease window action definitions, and be backward compatible. """
+ result = super(Lead2OpportunityPartner, self).default_get(fields)
+
+ if not result.get('lead_id') and self.env.context.get('active_id'):
+ result['lead_id'] = self.env.context.get('active_id')
+ return result
+
+ name = fields.Selection([
+ ('convert', 'Convert to opportunity'),
+ ('merge', 'Merge with existing opportunities')
+ ], 'Conversion Action', compute='_compute_name', readonly=False, store=True, compute_sudo=False)
+ action = fields.Selection([
+ ('create', 'Create a new customer'),
+ ('exist', 'Link to an existing customer'),
+ ('nothing', 'Do not link to a customer')
+ ], string='Related Customer', compute='_compute_action', readonly=False, store=True, compute_sudo=False)
+ lead_id = fields.Many2one('crm.lead', 'Associated Lead', required=True)
+ duplicated_lead_ids = fields.Many2many(
+ 'crm.lead', string='Opportunities', context={'active_test': False},
+ compute='_compute_duplicated_lead_ids', readonly=False, store=True, compute_sudo=False)
+ partner_id = fields.Many2one(
+ 'res.partner', 'Customer',
+ compute='_compute_partner_id', readonly=False, store=True, compute_sudo=False)
+ user_id = fields.Many2one(
+ 'res.users', 'Salesperson',
+ compute='_compute_user_id', readonly=False, store=True, compute_sudo=False)
+ team_id = fields.Many2one(
+ 'crm.team', 'Sales Team',
+ compute='_compute_team_id', readonly=False, store=True, compute_sudo=False)
+ force_assignment = fields.Boolean(
+ 'Force assignment', default=True,
+ help='If checked, forces salesman to be updated on updated opportunities even if already set.')
+
+ @api.depends('duplicated_lead_ids')
+ def _compute_name(self):
+ for convert in self:
+ if not convert.name:
+ convert.name = 'merge' if convert.duplicated_lead_ids and len(convert.duplicated_lead_ids) >= 2 else 'convert'
+
+ @api.depends('lead_id')
+ def _compute_action(self):
+ for convert in self:
+ if not convert.lead_id:
+ convert.action = 'nothing'
+ else:
+ partner = convert.lead_id._find_matching_partner()
+ if partner:
+ convert.action = 'exist'
+ elif convert.lead_id.contact_name:
+ convert.action = 'create'
+ else:
+ convert.action = 'nothing'
+
+ @api.depends('lead_id', 'partner_id')
+ def _compute_duplicated_lead_ids(self):
+ for convert in self:
+ if not convert.lead_id:
+ convert.duplicated_lead_ids = False
+ continue
+ convert.duplicated_lead_ids = self.env['crm.lead']._get_lead_duplicates(
+ convert.partner_id,
+ convert.lead_id.partner_id.email if convert.lead_id.partner_id.email else convert.lead_id.email_from,
+ include_lost=True).ids
+
+ @api.depends('action', 'lead_id')
+ def _compute_partner_id(self):
+ for convert in self:
+ if convert.action == 'exist':
+ convert.partner_id = convert.lead_id._find_matching_partner()
+ else:
+ convert.partner_id = False
+
+ @api.depends('lead_id')
+ def _compute_user_id(self):
+ for convert in self:
+ convert.user_id = convert.lead_id.user_id if convert.lead_id.user_id else False
+
+ @api.depends('user_id')
+ def _compute_team_id(self):
+ """ When changing the user, also set a team_id or restrict team id
+ to the ones user_id is member of. """
+ for convert in self:
+ # setting user as void should not trigger a new team computation
+ if not convert.user_id:
+ continue
+ user = convert.user_id
+ if convert.team_id and user in convert.team_id.member_ids | convert.team_id.user_id:
+ continue
+ team_domain = []
+ team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain)
+ convert.team_id = team.id
+
+ @api.model
+ def view_init(self, fields):
+ # JEM TDE FIXME: clean that brol
+ """ Check some preconditions before the wizard executes. """
+ for lead in self.env['crm.lead'].browse(self._context.get('active_ids', [])):
+ if lead.probability == 100:
+ raise UserError(_("Closed/Dead leads cannot be converted into opportunities."))
+ return False
+
+ def action_apply(self):
+ if self.name == 'merge':
+ result_opportunity = self._action_merge()
+ else:
+ result_opportunity = self._action_convert()
+
+ return result_opportunity.redirect_lead_opportunity_view()
+
+ def _action_merge(self):
+ to_merge = self.duplicated_lead_ids
+ result_opportunity = to_merge.merge_opportunity(auto_unlink=False)
+ result_opportunity.action_unarchive()
+
+ if result_opportunity.type == "lead":
+ self._convert_and_allocate(result_opportunity, [self.user_id.id], team_id=self.team_id.id)
+ else:
+ if not result_opportunity.user_id or self.force_assignment:
+ result_opportunity.write({
+ 'user_id': self.user_id.id,
+ 'team_id': self.team_id.id,
+ })
+ (to_merge - result_opportunity).unlink()
+ return result_opportunity
+
+ def _action_convert(self):
+ """ """
+ result_opportunities = self.env['crm.lead'].browse(self._context.get('active_ids', []))
+ self._convert_and_allocate(result_opportunities, [self.user_id.id], team_id=self.team_id.id)
+ return result_opportunities[0]
+
+ def _convert_and_allocate(self, leads, user_ids, team_id=False):
+ self.ensure_one()
+
+ for lead in leads:
+ if lead.active and self.action != 'nothing':
+ self._convert_handle_partner(
+ lead, self.action, self.partner_id.id or lead.partner_id.id)
+
+ lead.convert_opportunity(lead.partner_id.id, [], False)
+
+ leads_to_allocate = leads
+ if not self.force_assignment:
+ leads_to_allocate = leads_to_allocate.filtered(lambda lead: not lead.user_id)
+
+ if user_ids:
+ leads_to_allocate.handle_salesmen_assignment(user_ids, team_id=team_id)
+
+ def _convert_handle_partner(self, lead, action, partner_id):
+ # used to propagate user_id (salesman) on created partners during conversion
+ lead.with_context(default_user_id=self.user_id.id).handle_partner_assignment(
+ force_partner_id=partner_id,
+ create_missing=(action == 'create')
+ )
diff --git a/addons/crm/wizard/crm_lead_to_opportunity_mass.py b/addons/crm/wizard/crm_lead_to_opportunity_mass.py
new file mode 100644
index 00000000..9deb2929
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_to_opportunity_mass.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class Lead2OpportunityMassConvert(models.TransientModel):
+ _name = 'crm.lead2opportunity.partner.mass'
+ _description = 'Convert Lead to Opportunity (in mass)'
+ _inherit = 'crm.lead2opportunity.partner'
+
+ lead_id = fields.Many2one(required=False)
+ lead_tomerge_ids = fields.Many2many(
+ 'crm.lead', 'crm_convert_lead_mass_lead_rel',
+ string='Active Leads', context={'active_test': False},
+ default=lambda self: self.env.context.get('active_ids', []),
+ )
+ user_ids = fields.Many2many('res.users', string='Salespersons')
+ deduplicate = fields.Boolean('Apply deduplication', default=True, help='Merge with existing leads/opportunities of each partner')
+ action = fields.Selection(selection_add=[
+ ('each_exist_or_create', 'Use existing partner or create'),
+ ], string='Related Customer', ondelete={
+ 'each_exist_or_create': lambda recs: recs.write({'action': 'exist'}),
+ })
+ force_assignment = fields.Boolean(default=False)
+
+ @api.depends('duplicated_lead_ids')
+ def _compute_name(self):
+ for convert in self:
+ convert.name = 'convert'
+
+ @api.depends('lead_tomerge_ids')
+ def _compute_action(self):
+ for convert in self:
+ convert.action = 'each_exist_or_create'
+
+ @api.depends('lead_tomerge_ids')
+ def _compute_partner_id(self):
+ for convert in self:
+ convert.partner_id = False
+
+ @api.depends('user_ids')
+ def _compute_team_id(self):
+ """ When changing the user, also set a team_id or restrict team id
+ to the ones user_id is member of. """
+ for convert in self:
+ # setting user as void should not trigger a new team computation
+ if not convert.user_id and not convert.user_ids and convert.team_id:
+ continue
+ user = convert.user_id or convert.user_ids and convert.user_ids[0] or self.env.user
+ if convert.team_id and user in convert.team_id.member_ids | convert.team_id.user_id:
+ continue
+ team_domain = []
+ team = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=team_domain)
+ convert.team_id = team.id
+
+ @api.depends('lead_tomerge_ids')
+ def _compute_duplicated_lead_ids(self):
+ for convert in self:
+ duplicated = self.env['crm.lead']
+ for lead in convert.lead_tomerge_ids:
+ duplicated_leads = self.env['crm.lead']._get_lead_duplicates(
+ partner=lead.partner_id,
+ email=lead.partner_id and lead.partner_id.email or lead.email_from,
+ include_lost=False)
+ if len(duplicated_leads) > 1:
+ duplicated += lead
+ convert.duplicated_lead_ids = duplicated.ids
+
+ def _convert_and_allocate(self, leads, user_ids, team_id=False):
+ """ When "massively" (more than one at a time) converting leads to
+ opportunities, check the salesteam_id and salesmen_ids and update
+ the values before calling super.
+ """
+ self.ensure_one()
+ salesmen_ids = []
+ if self.user_ids:
+ salesmen_ids = self.user_ids.ids
+ return super(Lead2OpportunityMassConvert, self)._convert_and_allocate(leads, salesmen_ids, team_id=team_id)
+
+ def action_mass_convert(self):
+ self.ensure_one()
+ if self.name == 'convert' and self.deduplicate:
+ # TDE CLEANME: still using active_ids from context
+ active_ids = self._context.get('active_ids', [])
+ merged_lead_ids = set()
+ remaining_lead_ids = set()
+ for lead in self.lead_tomerge_ids:
+ if lead not in merged_lead_ids:
+ duplicated_leads = self.env['crm.lead']._get_lead_duplicates(
+ partner=lead.partner_id,
+ email=lead.partner_id.email or lead.email_from,
+ include_lost=False
+ )
+ if len(duplicated_leads) > 1:
+ lead = duplicated_leads.merge_opportunity()
+ merged_lead_ids.update(duplicated_leads.ids)
+ remaining_lead_ids.add(lead.id)
+ # rebuild list of lead IDS to convert, following given order
+ final_ids = [lead_id for lead_id in active_ids if lead_id not in merged_lead_ids]
+ final_ids += [lead_id for lead_id in remaining_lead_ids if lead_id not in final_ids]
+
+ self = self.with_context(active_ids=final_ids) # only update active_ids when there are set
+ return self.action_apply()
+
+ def _convert_handle_partner(self, lead, action, partner_id):
+ if self.action == 'each_exist_or_create':
+ partner_id = lead._find_matching_partner(email_only=True).id
+ action = 'create'
+ return super(Lead2OpportunityMassConvert, self)._convert_handle_partner(lead, action, partner_id)
diff --git a/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml b/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml
new file mode 100644
index 00000000..8fc3e1a9
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<odoo>
+ <record id="view_crm_lead2opportunity_partner_mass" model="ir.ui.view">
+ <field name="name">crm.lead2opportunity.partner.mass.form</field>
+ <field name="model">crm.lead2opportunity.partner.mass</field>
+ <field name="arch" type="xml">
+ <form string="Convert to Opportunity">
+ <field name="lead_tomerge_ids" invisible="1"/>
+ <separator string="Conversion Options"/>
+ <group>
+ <field name="name" class="oe_inline" widget="radio"/>
+ <field name="deduplicate" class="oe_inline"/>
+ </group>
+ <group string="Assign these opportunities to">
+ <field name="team_id" kanban_view_ref="%(sales_team.crm_team_view_kanban)s"/>
+ <field name="user_ids" widget="many2many_tags" domain="[('share', '=', False)]"/>
+ <field name="force_assignment"/>
+ </group>
+ <label for="duplicated_lead_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
+ <group attrs="{'invisible': [('deduplicate', '=', False)]}">
+ <field name="duplicated_lead_ids" colspan="4" nolabel="1" readonly="1">
+ <tree create="false" delete="false">
+ <field name="create_date" widget="date"/>
+ <field name="name"/>
+ <field name="type"/>
+ <field name="contact_name"/>
+ <field name="country_id" invisible="context.get('invisible_country', True)"/>
+ <field name="email_from"/>
+ <field name="stage_id"/>
+ <field name="user_id"/>
+ <field name="team_id"/>
+ </tree>
+ </field>
+ </group>
+ <group attrs="{'invisible': [('name', '!=', 'convert')]}" string="Customers" col="1">
+ <field name="action" class="oe_inline" widget="radio"/>
+ <group col="2">
+ <field name="partner_id"
+ widget="res_partner_many2one"
+ attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
+ context="{'show_vat': True}"
+ class="oe_inline"/>
+ </group>
+ </group>
+ <footer>
+ <button string="Convert to Opportunities" name="action_mass_convert" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_crm_send_mass_convert" model="ir.actions.act_window">
+ <field name="name">Convert to opportunities</field>
+ <field name="res_model">crm.lead2opportunity.partner.mass</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_crm_lead2opportunity_partner_mass"/>
+ <field name="target">new</field>
+ <field name="context">{}</field>
+ <field name="binding_model_id" ref="model_crm_lead"/>
+ <field name="binding_view_types">list</field>
+ </record>
+</odoo>
diff --git a/addons/crm/wizard/crm_lead_to_opportunity_views.xml b/addons/crm/wizard/crm_lead_to_opportunity_views.xml
new file mode 100644
index 00000000..2a379c19
--- /dev/null
+++ b/addons/crm/wizard/crm_lead_to_opportunity_views.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<odoo>
+ <record id="view_crm_lead2opportunity_partner" model="ir.ui.view">
+ <field name="name">crm.lead2opportunity.partner.form</field>
+ <field name="model">crm.lead2opportunity.partner</field>
+ <field name="arch" type="xml">
+ <form string="Convert to Opportunity">
+ <group name="name">
+ <field name="name" widget="radio"/>
+ </group>
+ <group string="Assign this opportunity to">
+ <field name="user_id" domain="[('share', '=', False)]"/>
+ <field name="team_id" widget="selection"/>
+ </group>
+ <group string="Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
+ <field name="lead_id" invisible="1"/>
+ <field name="duplicated_lead_ids" nolabel="1">
+ <tree>
+ <field name="create_date" widget="date"/>
+ <field name="name"/>
+ <field name="type"/>
+ <field name="contact_name"/>
+ <field name="country_id" invisible="context.get('invisible_country', True)"/>
+ <field name="email_from"/>
+ <field name="stage_id"/>
+ <field name="user_id"/>
+ <field name="team_id" kanban_view_ref="%(sales_team.crm_team_view_kanban)s"/>
+ </tree>
+ </field>
+ </group>
+ <group name="action" attrs="{'invisible': [('name', '!=', 'convert')]}" string="Customer" col="1">
+ <field name="action" nolabel="1" widget="radio"/>
+ <group col="2">
+ <field name="partner_id" widget="res_partner_many2one" context="{'res_partner_search_mode': 'customer', 'show_vat': True}" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"/>
+ </group>
+ </group>
+ <footer>
+ <button name="action_apply" string="Create Opportunity" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_crm_lead2opportunity_partner" model="ir.actions.act_window">
+ <field name="name">Convert to opportunity</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">crm.lead2opportunity.partner</field>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="view_crm_lead2opportunity_partner"/>
+ <field name="target">new</field>
+ </record>
+</odoo>
diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py
new file mode 100644
index 00000000..4c95d7db
--- /dev/null
+++ b/addons/crm/wizard/crm_merge_opportunities.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models
+
+
+class MergeOpportunity(models.TransientModel):
+ """
+ Merge opportunities together.
+ If we're talking about opportunities, it's just because it makes more sense
+ to merge opps than leads, because the leads are more ephemeral objects.
+ But since opportunities are leads, it's also possible to merge leads
+ together (resulting in a new lead), or leads and opps together (resulting
+ in a new opp).
+ """
+
+ _name = 'crm.merge.opportunity'
+ _description = 'Merge Opportunities'
+
+ @api.model
+ def default_get(self, fields):
+ """ 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')
+ 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['opportunity_ids'] = [(6, 0, opp_ids)]
+
+ return result
+
+ opportunity_ids = fields.Many2many('crm.lead', 'merge_opportunity_rel', 'merge_id', 'opportunity_id', string='Leads/Opportunities')
+ user_id = fields.Many2one('res.users', 'Salesperson', index=True)
+ team_id = fields.Many2one(
+ 'crm.team', 'Sales Team', index=True,
+ compute='_compute_team_id', readonly=False, store=True)
+
+ def action_merge(self):
+ self.ensure_one()
+ merge_opportunity = self.opportunity_ids.merge_opportunity(self.user_id.id, self.team_id.id)
+ return merge_opportunity.redirect_lead_opportunity_view()
+
+ @api.depends('user_id')
+ def _compute_team_id(self):
+ """ When changing the user, also set a team_id or restrict team id
+ to the ones user_id is member of. """
+ for wizard in self:
+ if wizard.user_id:
+ user_in_team = False
+ if wizard.team_id:
+ user_in_team = wizard.env['crm.team'].search_count([('id', '=', wizard.team_id.id), '|', ('user_id', '=', wizard.user_id.id), ('member_ids', '=', wizard.user_id.id)])
+ if not user_in_team:
+ wizard.team_id = wizard.env['crm.team'].search(['|', ('user_id', '=', wizard.user_id.id), ('member_ids', '=', wizard.user_id.id)], limit=1)
diff --git a/addons/crm/wizard/crm_merge_opportunities_views.xml b/addons/crm/wizard/crm_merge_opportunities_views.xml
new file mode 100644
index 00000000..4d34d43a
--- /dev/null
+++ b/addons/crm/wizard/crm_merge_opportunities_views.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<odoo>
+ <!-- Merge Opportunities -->
+ <record id="merge_opportunity_form" model="ir.ui.view">
+ <field name="name">crm.merge.opportunity.form</field>
+ <field name="model">crm.merge.opportunity</field>
+ <field name="arch" type="xml">
+ <form string="Merge Leads/Opportunities">
+ <group string="Assign opportunities to">
+ <field name="user_id" class="oe_inline"/>
+ <field name="team_id" class="oe_inline" kanban_view_ref="%(sales_team.crm_team_view_kanban)s"/>
+ </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" class="o_force_ltr"/>
+ <field name="stage_id"/>
+ <field name="user_id"/>
+ <field name="team_id"/>
+ </tree>
+ </field>
+ </group>
+ <footer>
+ <button name="action_merge" type="object" string="Merge" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_merge_opportunities" model="ir.actions.act_window">
+ <field name="name">Merge</field>
+ <field name="res_model">crm.merge.opportunity</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ <field name="binding_model_id" ref="model_crm_lead"/>
+ <field name="binding_view_types">list</field>
+ </record>
+
+</odoo>