diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/crm/wizard | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/crm/wizard')
| -rw-r--r-- | addons/crm/wizard/__init__.py | 7 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_lost.py | 14 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_lost_views.xml | 27 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_to_opportunity.py | 170 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_to_opportunity_mass.py | 110 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_to_opportunity_mass_views.xml | 63 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_lead_to_opportunity_views.xml | 53 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_merge_opportunities.py | 56 | ||||
| -rw-r--r-- | addons/crm/wizard/crm_merge_opportunities_views.xml | 45 |
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> |
