mirror of
https://github.com/bringout/oca-ocb-crm.git
synced 2026-04-25 00:52:02 +02:00
Initial commit: Crm packages
This commit is contained in:
commit
21a345b5b9
654 changed files with 418312 additions and 0 deletions
8
odoo-bringout-oca-ocb-crm/crm/wizard/__init__.py
Normal file
8
odoo-bringout-oca-ocb-crm/crm/wizard/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# -*- 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
|
||||
from . import crm_lead_pls_update
|
||||
28
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_lost.py
Normal file
28
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_lost.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, _
|
||||
from odoo.tools.mail import is_html_empty
|
||||
|
||||
|
||||
class CrmLeadLost(models.TransientModel):
|
||||
_name = 'crm.lead.lost'
|
||||
_description = 'Get Lost Reason'
|
||||
|
||||
lost_reason_id = fields.Many2one('crm.lost.reason', 'Lost Reason')
|
||||
lost_feedback = fields.Html(
|
||||
'Closing Note', sanitize=True
|
||||
)
|
||||
|
||||
def action_lost_reason_apply(self):
|
||||
self.ensure_one()
|
||||
leads = self.env['crm.lead'].browse(self.env.context.get('active_ids'))
|
||||
if not is_html_empty(self.lost_feedback):
|
||||
leads._track_set_log_message(
|
||||
'<div style="margin-bottom: 4px;"><p>%s:</p>%s<br /></div>' % (
|
||||
_('Lost Comment'),
|
||||
self.lost_feedback
|
||||
)
|
||||
)
|
||||
res = leads.action_set_lost(lost_reason_id=self.lost_reason_id.id)
|
||||
return res
|
||||
31
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_lost_views.xml
Normal file
31
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_lost_views.xml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?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>
|
||||
<field name="lost_reason_id" options="{'no_create_edit': True}" />
|
||||
</group>
|
||||
<field name="lost_feedback" placeholder="What went wrong ?"/>
|
||||
<footer>
|
||||
<button name="action_lost_reason_apply" string="Submit" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
<field name="context">{
|
||||
'dialog_size' : 'medium',
|
||||
}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
36
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_pls_update.py
Normal file
36
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_pls_update.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class CrmUpdateProbabilities(models.TransientModel):
|
||||
_name = 'crm.lead.pls.update'
|
||||
_description = "Update the probabilities"
|
||||
|
||||
def _get_default_pls_start_date(self):
|
||||
pls_start_date_config = self.env['ir.config_parameter'].sudo().get_param('crm.pls_start_date')
|
||||
return fields.Date.to_date(pls_start_date_config)
|
||||
|
||||
def _get_default_pls_fields(self):
|
||||
pls_fields_config = self.env['ir.config_parameter'].sudo().get_param('crm.pls_fields')
|
||||
if pls_fields_config:
|
||||
names = pls_fields_config.split(',')
|
||||
fields = self.env['ir.model.fields'].search([('name', 'in', names), ('model', '=', 'crm.lead')])
|
||||
return self.env['crm.lead.scoring.frequency.field'].search([('field_id', 'in', fields.ids)])
|
||||
else:
|
||||
return None
|
||||
|
||||
pls_start_date = fields.Date(required=True, default=_get_default_pls_start_date)
|
||||
pls_fields = fields.Many2many('crm.lead.scoring.frequency.field', default=_get_default_pls_fields)
|
||||
|
||||
def action_update_crm_lead_probabilities(self):
|
||||
if self.env.user._is_admin():
|
||||
set_param = self.env['ir.config_parameter'].sudo().set_param
|
||||
if self.pls_fields:
|
||||
pls_fields_str = ','.join(self.pls_fields.mapped('field_id.name'))
|
||||
set_param('crm.pls_fields', pls_fields_str)
|
||||
else:
|
||||
set_param('crm.pls_fields', "")
|
||||
set_param('crm.pls_start_date', str(self.pls_start_date))
|
||||
self.env['crm.lead'].sudo()._cron_update_automated_probabilities()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="crm_lead_pls_update_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.lead.pls.update.view.form</field>
|
||||
<field name="model">crm.lead.pls.update</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<p>
|
||||
The success rate is computed based on the stage, but you can add more fields in the statistical analysis.
|
||||
</p>
|
||||
<p>
|
||||
<field name="pls_fields" widget="many2many_tags" placeholder="Extra fields..."/>
|
||||
</p>
|
||||
<p>
|
||||
Consider leads created as of the: <field name="pls_start_date"/>
|
||||
</p>
|
||||
<footer>
|
||||
<button name="action_update_crm_lead_probabilities" type="object"
|
||||
string="Confirm" class="btn-primary" data-hotkey="q"/>
|
||||
<button special="cancel" data-hotkey="z" string="Cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_lead_pls_update_action" model="ir.actions.act_window">
|
||||
<field name="name">Update Probabilities</field>
|
||||
<field name="res_model">crm.lead.pls.update</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="view_id" ref="crm_lead_pls_update_view_form"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
167
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_to_opportunity.py
Normal file
167
odoo-bringout-oca-ocb-crm/crm/wizard/crm_lead_to_opportunity.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# -*- 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')
|
||||
|
||||
if result.get('lead_id'):
|
||||
if self.env['crm.lead'].browse(result['lead_id']).probability == 100:
|
||||
raise UserError(_("Closed/Dead leads cannot be converted into opportunities."))
|
||||
|
||||
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 = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=None)
|
||||
convert.team_id = team.id
|
||||
|
||||
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,
|
||||
})
|
||||
if self.lead_id != result_opportunity:
|
||||
# Prevent unwanted cascade during unlinks, keeping other operations and overrides possible
|
||||
self.write({'lead_id': result_opportunity})
|
||||
(to_merge - result_opportunity).sudo().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, user_ids=False, team_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')
|
||||
)
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# -*- 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 = self.env['crm.team']._get_default_team_id(user_id=user.id, domain=None)
|
||||
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)
|
||||
|
|
@ -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)" options="{'no_open': True, 'no_create': 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" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
|
|
@ -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" options="{'no_open': True, 'no_create': True}" kanban_view_ref="%(sales_team.crm_team_view_kanban)s"/>
|
||||
</group>
|
||||
<group string="Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
|
||||
<field name="lead_id" invisible="1"/>
|
||||
<field name="duplicated_lead_ids" colspan="2" 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)" options="{'no_open': True, 'no_create': 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" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
|
|
@ -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', domain="[('share', '=', False)]")
|
||||
team_id = fields.Many2one(
|
||||
'crm.team', 'Sales Team',
|
||||
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)
|
||||
|
|
@ -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" optional="hide"/>
|
||||
<field name="phone" class="o_force_ltr" optional="hide"/>
|
||||
<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" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue